概念:
AOP是Aspect Oriented Programming的缩写,意思是面向切面编程
功能:
日志记录,性能统计,安全控制,事务处理,异常处理等
原理:
AOP通过反射机制实现动态代理,具体看下面举例吧。
举例:
在业务方法执行前后加日志记录
业务类接口IHello.java
package demo.aop; public interface IHello { /** * 业务处理A * * @param name */ void sayHello(String name); /** * 业务处理B * * @param name */ void sayGoodBye(String name); }
IHello.java的一个实现类
package demo.aop; public class Hello implements IHello{ public void sayHello(String name) { System.out.println("hello " + name); } public void sayGoodBye(String name) { System.out.println(name + " goodbye!"); } }
日志记录相关的类:Logger类和Level枚举
package demo.aop; public enum Level { DEBUG, INFO }
package demo.aop; import java.util.Date; public class Logger { public static void logging(Level level, String context) { if (Level.DEBUG.equals(level)) { System.out.println("DEBUG\t" + new Date().toString() + "\t" + context); } else if (Level.INFO.equals(level)) { System.out.println("INFO\t" + new Date().toString() + "\t" + context); } } }
下面我们为这个sayHello业务方法加上日志记录的业务,我们在不改变原代码的情况下实现该功能
思路1:写一个类实现IHello接口,并依赖Hello这个类
package demo.aop; /** * Hello代理类 * * @author ydc * @date 6:55:42 PM Jul 31, 2013 */ public class ProxyHello implements IHello { private IHello hello; public ProxyHello(IHello hello) { this.hello = hello; } public void sayHello(String name) { Logger.logging(Level.DEBUG, "sayHello method start"); hello.sayHello(name); Logger.logging(Level.INFO, "sayHello method end"); } public void sayGoodBye(String name) { hello.sayGoodBye(name); } }
测试一下
package demo.aop; public class AopTest { public static void main(String[] args) { new AopTest().test1(); } public void test1() { // 无日志记录功能 IHello hello1 = new Hello(); // 有日志记录功能 IHello hello2 = new ProxyHello(new Hello()); hello1.sayHello("wallet white"); System.out.println("------------------------------"); hello2.sayHello("wallet white"); } }
结果输出:
hello wallet white ------------------------------ DEBUG Wed Jul 31 21:46:38 CST 2013 sayHello method start hello wallet white INFO Wed Jul 31 21:46:38 CST 2013 sayHello method end
缺点:如果像Hello这样的类很多,那么我们就要写很多个ProxyHello这样的类
思路2:在jdk1.3以后,jdk提供了一个API java.lang.reflect.InvocationHandler的类,这个类可以让我们在JVM调用某个类的方法时动态的为这些方法做些其他事
写一个代理类实现DynaProxyHello实现InvocationHandler接口
package demo.aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * Hello动态代理类 代理对象与被代理对像解藕 * * @author ydc * @date 6:56:27 PM Jul 31, 2013 */ public class DynaProxyHello implements InvocationHandler { /** * 要处理的对象 */ private Object delegate; /** * 动态生成方法被处理过后的对象(写法固定) * * @param delegate * @return Object */ public Object bind(Object delegate) { this.delegate = delegate; return Proxy.newProxyInstance( this.delegate.getClass().getClassLoader(), this.delegate .getClass().getInterfaces(), this); } /** * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说, 要处理的对象的方法只能通过此方法調用,此方法是动态的 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { // 执行原来的方法之前记录日志 Logger.logging(Level.DEBUG, method.getName() + " method start"); // JVM通过这条语句执行原来的方法(反射机制) result = method.invoke(this.delegate, args); // 执行原来的方法之后记录日志 Logger.logging(Level.INFO, method.getName() + " method end"); } catch (Exception e) { e.printStackTrace(); } // 返回方法返回值给调用者 return result; } }
测试一下
package demo.aop; public class AopTest { public static void main(String[] args) { new AopTest().test2(); } public void test2() { IHello hello = (IHello) new DynaProxyHello().bind(new Hello()); hello.sayHello("wallet white"); System.out.println("------------------"); hello.sayGoodBye("wallet white"); } }
结果输出:
DEBUG Wed Jul 31 21:57:49 CST 2013 sayHello method start hello wallet white INFO Wed Jul 31 21:57:49 CST 2013 sayHello method end ------------------ DEBUG Wed Jul 31 21:57:49 CST 2013 sayGoodBye method start wallet white goodbye! INFO Wed Jul 31 21:57:49 CST 2013 sayGoodBye method end
由此可知,采用面向接口编程,对于任何对象的方法执行之前要加上记录日志的操作都是可以的。
代理对象(DynaProxyHello)自动去代理执行被代理对象(Hello)中的每一个方法,一个InvocationHandler接口就把我们的代理对象和被代理对象解藕了。
问题:要是为不同的业务方法前加的日志记录不同,就需要写多个DynaProxyHello类
加强版:把DynaProxyHello对象和日志操作对象(Logger)解藕
我们要在被代理对象的方法前面或者后面加上日志操作代码 ,那么我们可以抽象一个接口,该接口就只有两个方法
package demo.aop; import java.lang.reflect.Method; public interface IOperation { /** * 方法执行之前的操作 * * @param method */ void start(Method method); /** * 方法执行之后的操作 * * @param method */ void end(Method method); }
写一个实现上面接口的类,把他作为真正的操作者,
package demo.aop; import java.lang.reflect.Method; /** * 该类将代理者与操作者解藕 * * @author ydc * @date 7:22:24 PM Jul 31, 2013 */ public class LoggerOperation implements IOperation { public void end(Method method) { Logger.logging(Level.DEBUG, method.getName() + "method end"); } public void start(Method method) { Logger.logging(Level.INFO, method.getName() + "method start"); } }
新建一个代理对象DynaProxyHello2
package demo.aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * Hello动态代理类 代理对象与被代理对像解藕 * * @author ydc * @date 6:56:27 PM Jul 31, 2013 */ public class DynaProxyHello2 implements InvocationHandler { /** * 操作者 */ private Object proxy; /** * 要处理的对象 */ private Object delegate; /** * 动态生成方法被处理过后的对象(写法固定) * * @param delegate * @param proxy * @return Object */ public Object bind(Object delegate, Object proxy) { this.delegate = delegate; this.proxy = proxy; return Proxy.newProxyInstance( this.delegate.getClass().getClassLoader(), this.delegate .getClass().getInterfaces(), this); } /** * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说, 要处理的对象的方法只能通过此方法調用,此方法是动态的 */ @SuppressWarnings("unchecked") public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { // 反射得到操作者的实例 Class clazz = this.proxy.getClass(); // 反射得到操作者的start方法 Method start = clazz.getDeclaredMethod("start", new Class[]{Method.class}); // 反射执行start方法 start.invoke(this.proxy, new Object[]{method}); // JVM通过这条语句执行原来的方法(反射机制) result = method.invoke(this.delegate, args); // 反射得到操作者的end方法 Method end = clazz.getDeclaredMethod("end", new Class[]{Method.class}); // 反射执行end方法 end.invoke(this.proxy, new Object[]{method}); } catch (Exception e) { e.printStackTrace(); } // 返回方法返回值给调用者 return result; } }
测试一下
package demo.aop; public class AopTest { public static void main(String[] args) { new AopTest().test3(); } public void test3() { IHello hello = (IHello) new DynaProxyHello2().bind(new Hello(), new LoggerOperation()); hello.sayHello("wallet white"); System.out.println("------------------"); hello.sayGoodBye("wallet white"); } }
结果输出:
INFO Wed Jul 31 22:12:08 CST 2013 sayHellomethod start hello wallet white DEBUG Wed Jul 31 22:12:08 CST 2013 sayHellomethod end ------------------ INFO Wed Jul 31 22:12:08 CST 2013 sayGoodByemethod start wallet white goodbye! DEBUG Wed Jul 31 22:12:08 CST 2013 sayGoodByemethod end
本文主要参考了下面列出的参考文献的第2篇文章
文章最后留下一个问题:如果不想让所有方法都被日志记录,我们应该怎么去解藕?
也给了一个明确的思路:将需要日志记录的方法写在配置文件里
我在这里做法如下:
---------------------------------------------------------------------------------------------
在馆内添加一个配置文件aop.xml(demo.aop)
<?xml version="1.0" encoding="gbk"?> <aop> <clazz name="demo.aop.Hello"> <method name="sayHello" /> </clazz> <clazz name="demo.aop.Hello2"> <method name="sayHello" /> <method name="sayGoodBye" /> </clazz> </aop>
在这里是完全匹配,由配置可以看出,对于类demo.aop.Hello内的sayHello方法和类demo.aop.Hello2内的sayHello,sayGoodBye方法添加日志记录功能
添加一个读取配置文件的工具类
package demo.aop; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class AopUtil { private static Map<String, List<String>> aopMap = null; public static Map<String, List<String>> initConfig() { if (aopMap != null) { return aopMap; } aopMap = new HashMap<String, List<String>>(); String filePath = Class.class.getClass().getResource( "/demo/aop/aop.xml").getPath(); File f = new File(filePath); Element element = null; DocumentBuilder db = null; DocumentBuilderFactory dbf = null; try { dbf = DocumentBuilderFactory.newInstance(); db = dbf.newDocumentBuilder(); Document dt = db.parse(f); element = dt.getDocumentElement(); // 根元素 aop NodeList clazzNodes = element.getChildNodes(); // 根元素下的子节点 clazz int cLen = clazzNodes.getLength(); // 遍历包名.类名 clazz for (int i = 0; i < cLen; i++) { Node node1 = clazzNodes.item(i); if ("clazz".equals(node1.getNodeName())) { String clazzName = node1.getAttributes().getNamedItem( "name").getNodeValue(); NodeList nodeDetail = node1.getChildNodes(); List<String> methodList = new ArrayList<String>(); // 遍历方法名 for (int j = 0; j < nodeDetail.getLength(); j++) { Node detail = nodeDetail.item(j); if ("method".equals(detail.getNodeName())) { String methodName = detail.getAttributes() .getNamedItem("name").getNodeValue(); methodList.add(methodName); } } aopMap.put(clazzName, methodList); } } } catch (Exception e) { e.printStackTrace(); } // for (Map.Entry<String, List<String>> entry : aopMap.entrySet()) { // System.out.println(entry.getKey()); // List<String> mList = entry.getValue(); // if (mList != null) // for (String s : mList) { // System.out.println(s); // } // } return aopMap; } }
修改动态代理类
package demo.aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import java.util.Map; /** * Hello动态代理类 代理对象与被代理对像解藕 * * @author ydc * @date 6:56:27 PM Jul 31, 2013 */ public class DynaProxyHello3 implements InvocationHandler { /** * 操作者 */ private Object proxy; /** * 要处理的对象 */ private Object delegate; /** * 动态生成方法被处理过后的对象(写法固定) * * @param delegate * @param proxy * @return Object */ public Object bind(Object delegate, Object proxy) { this.delegate = delegate; this.proxy = proxy; return Proxy.newProxyInstance( this.delegate.getClass().getClassLoader(), this.delegate .getClass().getInterfaces(), this); } /** * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说, 要处理的对象的方法只能通过此方法調用,此方法是动态的 */ @SuppressWarnings("unchecked") public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { // 初始化配置文件 Map<String, List<String>> aopMap = AopUtil.initConfig(); // 要执行的方法所在包与类名 String clazzName = this.delegate.getClass().getName(); String methodName = method.getName(); // 是否调用日志记录的标志 boolean isOpen = false; for (Map.Entry<String, List<String>> entry : aopMap.entrySet()) { // 匹配方法所在的包名与类名 if (clazzName.equals(entry.getKey())) { List<String> mList = entry.getValue(); if (mList != null) { for (String m : mList) { if (methodName.equals(m)) { isOpen = true; break; } } } } } // 反射得到操作者的实例 Class clazz = this.proxy.getClass(); if (isOpen) { // 反射得到操作者的start方法 Method start = clazz.getDeclaredMethod("start", new Class[]{Method.class}); // 反射执行start方法 start.invoke(this.proxy, new Object[]{method}); } // JVM通过这条语句执行原来的方法(反射机制) result = method.invoke(this.delegate, args); if (isOpen) { // 反射得到操作者的end方法 Method end = clazz.getDeclaredMethod("end", new Class[]{Method.class}); // 反射执行end方法 end.invoke(this.proxy, new Object[]{method}); } } catch (Exception e) { e.printStackTrace(); } // 返回方法返回值给调用者 return result; } }
这样,再对上述代码进行测试:
package demo.aop; public class AopTest { public static void main(String[] args) { new AopTest().test4(); } public void test4() { IHello hello = (IHello) new DynaProxyHello3().bind(new Hello(), new LoggerOperation()); hello.sayHello("wallet white"); System.out.println("------------------"); hello.sayGoodBye("wallet white"); } }
结果输出:
INFO Thu Aug 01 09:35:03 CST 2013 sayHellomethod start hello wallet white DEBUG Thu Aug 01 09:35:03 CST 2013 sayHellomethod end ------------------ wallet white goodbye!
在这里我们看到 Hello类内的sayGoodBye方法没有日志记录功能。
参考:
1.http://baike.baidu.com/view/73626.htm
2.http://www.blogjava.net/DoubleJ/archive/2008/03/04/183796.html
3.http://www.cnblogs.com/200911/archive/2012/10/09/2716882.html
相关推荐
在使用反射实现AOP动态代理时,我们也需要注意一些问题。例如,由于代理类是在运行时动态创建的,这可能会影响程序的性能,特别是在频繁调用代理方法的情况下。因此,在实际开发中,需要根据应用场景的性能要求和...
Spring框架是Java中实现AOP的一个流行工具,它通过动态代理机制实现了这一功能。本文将深入探讨Spring AOP的实现原理,以及如何使用反射来实现动态代理模式。 首先,我们需要了解AOP的基本概念。AOP的核心思想是切...
动态代理是实现AOP的一种常见方式,特别是在Java中。 在Java中,动态代理主要涉及到两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。Proxy类用于创建一个实现了特定接口的代理对象,而...
### Java反射与代理实现AOP #### 一、AOP概念及应用场景 AOP(Aspect-Oriented Programming,面向切面编程)是一种编程思想和技术,主要用于处理横切关注点问题,比如日志记录、性能统计、安全控制、事务处理、...
JDK动态代理基于Java的反射API实现,适用于接口代理。当目标对象实现了至少一个接口时,Spring会创建一个代理对象,这个代理对象实现了与目标对象相同的接口,并在调用接口方法时插入额外的切面逻辑。代理对象在运行...
动态代理则是Spring AOP实现的核心技术之一,它允许我们在运行时创建具有额外行为的对象。下面将详细阐述Spring AOP的配置以及动态代理的实现。 一、Spring AOP基础知识 1. **什么是AOP**:AOP是一种编程范式,...
在 Java 中,我们可以使用反射机制和动态代理机制来实现 AOP。Java 的反射机制可以让我们在运行时动态地获取和调用类的信息,而动态代理机制可以让我们在不修改业务逻辑代码的情况下,能够对业务逻辑方法进行拦截和...
以下是一个简单的例子,展示了如何使用Java动态代理实现AOP: ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface MyInterface {...
例如,`Castle.DynamicProxy`库就是基于动态代理实现AOP的。动态代理在运行时创建代理类,可以针对接口或者抽象类,而不需要重新编译源代码。这使得代码更加灵活,但性能相比静态织入略低。 3. **特性驱动编程**: ...
Spring AOP 的底层实现技术 --- Jdk 动态代理原理 JDK 动态代理是 Spring AOP 的底层实现技术,允许开发者在运行期创建接口的代理实例。在 JDK 1.3 以后,JDK 动态代理技术提供了实现 AOP 的绝好底层技术。JDK 动态...
使用动态代理实现AOP需要有四个角色:被代理的类,被代理类的接口,织入器,和InvocationHandler,而织入器使用接口反射机制生成一个代理类,然后在这个代理类中织入代码。被代理的类是AOP里所说的目标,...
它通过反射机制在运行时生成一个实现了所有目标接口的代理类实例,代理类会调用InvocationHandler接口的invoke方法来转发方法调用。 3. **CGLIB代理**:如果目标对象没有实现任何接口,Spring将使用CGLIB(Code ...
在实际应用中,Spring AOP(面向切面编程)就是利用动态代理技术实现的。它可以让我们编写关注点分离的代码,比如日志、事务等通用功能,而无需侵入业务逻辑。此外,动态代理也被广泛应用于RPC框架,如Dubbo、gRPC,...
5. AOP代理的使用:当通过Spring的依赖注入(DI)获取到一个对象时,实际上得到的是一个代理对象。通过这个代理对象调用方法,Spring会自动插入预先定义好的通知逻辑。 总的来说,JDK动态代理是Spring AOP实现的...
Spring框架广泛使用AOP来实现这些功能,而Java的反射和动态代理机制是实现AOP的基础。 首先,AOP的核心思想是将横切关注点(如日志、事务管理)从核心业务逻辑中分离出来,形成独立的模块,以提高代码的可读性和可...
本主题将深入探讨JVM技术,特别是反射与动态代理这两个关键特性。 一、JVM技术 1. 类加载机制:JVM通过类加载器(ClassLoader)来加载.class文件,分为启动类加载器、扩展类加载器和应用程序类加载器。类的加载...
Java反射和动态代理是Java编程中的重要特性,它们在实现高度灵活和动态的代码执行方面发挥着关键作用。本文将深入探讨这两个概念及其在实际开发中的应用。 **Java反射** Java反射API允许程序在运行时检查类、接口...
Spring AOP采用基于代理的方式实现AOP,这使得它在J2EE环境下的应用变得非常广泛。 综上所述,AOP技术通过提供一套丰富的编程范式和机制,帮助开发者在保持业务逻辑清晰的同时,也能够有效地管理系统的横切关注点,...