AOP学习一:JDK动态代理

AOP,Aspect Oriented Programming,就是所谓的面向切面编程。这个词语的含义,网上的解释已经很多很多了,理解起来并不怎么难,照葫芦画瓢也能开发出带aop功能的模块出来。开始我也是这样,但只是会用,对它的原理却并没有做过深入地学习。之前一段时间挺闲,于是就打算从头开始认认真真地去研究下aop的原理。于是便有了如下的一套学习笔记,分享出来,只要照着上面的代码敲一遍,相信也会对aop的实现有所掌握。

在开始aop学习之前,先来说下两种动态代理技术:JDK动态代理及CGLib动态代理。aop便是以这两者为基础,实现了切面编程的功能。

何为代理?当一个类被AOP织入增强后,就产出了一个代理类,这个类融合了原类和一些需要添加的增强逻辑。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。

Spring AOP使用了两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理。之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理。下面依次来讲述这两种动态代理的实现。

JDK动态代理

JDK的动态代理主要涉及到java.lang.reflect包中的两个类:ProxyInvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口来定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。下面给出使用示例:

业务接口:
[java]  view plain copy
  1. package proxy.jdk;  
  2.   
  3. public interface ForumService {  
  4.     void removeTopic(int topicId);  
  5.     void removeForum(int forumId);  
  6. }  

业务接口实现类:
[java]  view plain copy
  1. package proxy.jdk;  
  2.   
  3. public class ForumServiceImpl implements ForumService {  
  4.   
  5.     @Override  
  6.     public void removeTopic(int topicId) {  
  7.         System.out.println("模拟删除Topic记录:" + topicId);  
  8.         try {  
  9.             Thread.sleep(20);  
  10.         } catch (Exception e) {  
  11.             throw new RuntimeException(e);  
  12.         }  
  13.     }  
  14.   
  15.     @Override  
  16.     public void removeForum(int forumId) {  
  17.         System.out.println("模拟删除Forum记录:" + forumId);  
  18.         try {  
  19.             Thread.sleep(40);  
  20.         } catch (Exception e) {  
  21.             throw new RuntimeException(e);  
  22.         }  
  23.     }  
  24.   
  25. }  

上面的实现类中,只是简单地实现了接口中定义的方法。现在,我们要实现这样的功能:如果这两个方法被调用,则记录下调用日志,并且简单计算下执行时间。如果照正常的逻辑来处理,那应该是类似下面的逻辑代码:
[java]  view plain copy
  1. @Override  
  2. public void removeTopic(int topicId) {  
  3.     System.out.println("调用目标对象方法 [removeTopic]开始...");  
  4.     long start = System.currentTimeMillis();  
  5.     System.out.println("模拟删除Topic记录:" + topicId);  
  6.     try {  
  7.         Thread.sleep(20);  
  8.     } catch (Exception e) {  
  9.         throw new RuntimeException(e);  
  10.     }  
  11.     long end = System.currentTimeMillis();  
  12.     System.out.println("调用目标对象方法[removeTopic]结束.总共花费了: " + (end - start) + "ms");  
  13. }  

日志的记录及时间的计算代码包围在核心的业务代码外,并且同样的代码实现需要对removeForum这个方法再写一遍。可想而知,如果有多个方法要处理,这样的重复处理的代码需要写多次。既浪费时间,又使代码变得臃肿。现在,我们通过JDK动态代理技术,来实现上面的功能:

实现了InvocationHandler接口的类:
[java]  view plain copy
  1. package proxy.jdk;  
  2.   
  3. import java.lang.reflect.InvocationHandler;  
  4. import java.lang.reflect.Method;  
  5.   
  6. public class PerformationHandler implements InvocationHandler {  
  7.     // 目标业务类  
  8.     private Object target;  
  9.   
  10.     public PerformationHandler(Object target) {  
  11.         this.target = target;  
  12.     }  
  13.   
  14.     /** 
  15.      * 将横切逻辑代码与业务代码编织到一起 
  16.      */  
  17.     @Override  
  18.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  19.         String methodName = target.getClass().getName() + "." +method.getName();  
  20.         System.out.println("调用目标对象方法 [" + methodName + "]开始...");  
  21.         long start = System.currentTimeMillis();  
  22.         // 通过反射调用目标类的目标方法  
  23.         Object obj = method.invoke(target, args);  
  24.         long end = System.currentTimeMillis();  
  25.         System.out.println("调用目标对象方法[" + methodName + "]结束.总共花费了: " + (end - start) + "ms");  
  26.         return obj;  
  27.     }  
  28.   
  29. }  

InvocationHandler接口定义了一个invoke()方法,proxy是最终生成的代理实例,一般不会用到;method是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用;args是通过被代理实例某一方法的入参,在方法反射调用时使用。

按照我们正常调用的逻辑,实现代码应该如下:
[java]  view plain copy
  1. // 正常的业务实现  
  2. ForumService forumService = new ForumServiceImpl();   
  3. forumService.removeTopic(10);  
  4. forumService.removeForum(13);  

但现在我们使用了动态代理,因此就通过代理来调用。

通过Proxy结合PerformationHandler创建业务接口的代理实例:
[java]  view plain copy
  1. // 通过动态代理来实现  
  2. ForumService target = new ForumServiceImpl();  
  3. PerformationHandler handler = new PerformationHandler(target);  
  4. ForumService proxy = (ForumService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target  
  5.         .getClass().getInterfaces(), handler);  
  6. proxy.removeForum(10);  
  7. proxy.removeTopic(12);  


通过Proxy的newProxyInstance()静态方法为混合了业务处理逻辑和性能监视逻辑的handler创建一个符合ForumService接口的代理实例。该方法的第一个入参为类加载器;第二个入参为创建代理所需要实现的一组接口;第三个参数是整合了业务逻辑和性能监视逻辑(这里即所谓的横切逻辑)的编织器对象。生成的代理实例实现了目标业务类的所有接口,即ForumServiceImpl的ForumService接口。这样,我们就可以按照调用ForumService接口实现相同的方式调用代理实例。运行以上代码,输出结果如下:

调用目标对象方法 [proxy.jdk.ForumServiceImpl.removeForum]开始...
模拟删除Forum记录:10
调用目标对象方法[proxy.jdk.ForumServiceImpl.removeForum]结束.总共花费了: 40ms
调用目标对象方法 [proxy.jdk.ForumServiceImpl.removeTopic]开始...
模拟删除Topic记录:12
调用目标对象方法[proxy.jdk.ForumServiceImpl.removeTopic]结束.总共花费了: 20ms

我们发现,程序的运行效果和直接在业务类中编写性能监视逻辑的效果一致,但是在这里,原来分散的横切逻辑代码已经被抽取到PerformationHandler中,当调用代理对象的removeForum()和removeTopic()方法时,便调用了invoke()方法。

你可能感兴趣的:(动态代理,spring,AOP,JDK动态代理)