AOP(Aspect Oriented Programing),面向切面方程。介绍具体定义前,先看一个例子:
1 package com.baobaotao.concept; 2 3 public class ForumService { 4 private TransactionManager transManager; 5 private PerformanceMonitor pmonitor; 6 private TopicDao topicDao; 7 private ForumDao forumDao; 8 9 public void removeTopic(int topicId) { 10 pmonitor.start(); 11 transManager.beginTransaction(); 12 13 topicDao.removeTopic(topicId); 14 15 transManager.endTransaction(); 16 pmonitor.end(); 17 } 18 public void CreateForum(Forum forum) { 19 pmonitor.start(); 20 transManager.beginTransaction(); 21 22 forumDao.create(forum); 23 24 transManager.endTransaction(); 25 pmonitor.end(); 26 } 27 }
上面代码中,10、11、15、16行和19、20、24、25行是重复的。这是一个监视程序,ForumService 中真正工作的方法是第13行和第22行。我们希望把重复的代码逻辑从原来的方法中抽离出来变成一个独立的模块,而工作类被抽离后变成下面这样:
1 package com.baobaotao.concept; 2 3 public class ForumService { 4 5 private TopicDao topicDao; 6 private ForumDao forumDao; 7 8 public void removeTopic(int topicId) { 9 //-------------------------------------------- 10 topicDao.removeTopic(topicId); 11 //-------------------------------------------- 12 } 13 public void CreateForum(Forum forum) { 14 //-------------------------------------------- 15 forumDao.create(forum); 16 //--------------------------------------------- 17 } 18 }
当第8行或第15行开始工作的时候,又能够把刚才抽离出来的独立的模块插进去,这就是AOP的任务。它就像一个切面的树的年轮,而一层层的圆环就是要抽离出来的东西,真正工作的代码就是圆心。我们把圆环抽出来,在圆心工作的时候,再把圆环插进去。所以,AOP名曰:面向切面方程。
1 package com.baobaotao.proxy; 2 3 public class ForumServiceImpl implements ForumService { 4 5 public void removeTopic(int topicId) { 6 PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeTopic"); 7 System.out.println("模拟删除Topic记录:"+topicId); 8 try { 9 Thread.currentThread().sleep(20); 10 } catch (Exception e) { 11 throw new RuntimeException(e); 12 } 13 PerformanceMonitor.end(); 14 } 15 16 public void removeForum(int forumId) { 17 PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeForum"); 18 System.out.println("模拟删除Forum记录:"+forumId); 19 try { 20 Thread.currentThread().sleep(40); 21 } catch (Exception e) { 22 throw new RuntimeException(e); 23 } 24 PerformanceMonitor.end(); 25 } 26 }
第6行和第13行是一个性能监视功能的简单调用。上面的代码很简单,两个方法都有着相同的性能监视代码( 可用作增强的代码 ),而之间包裹着具体的打印代码。
下面是性能监视的简单实现类( 使用了ThreadLocal保证线程安全 )。
1 package com.baobaotao.proxy; 2 3 public class PerformanceMonitor { 4 private static ThreadLocal<MethodPerformace> performaceRecord = new ThreadLocal<MethodPerformace>(); 5 public static void begin(String method) { 6 System.out.println("begin monitor..."); 7 MethodPerformace mp = performaceRecord.get(); 8 if(mp == null){ 9 mp = new MethodPerformace(method); 10 performaceRecord.set(mp); 11 }else{ 12 mp.reset(method); 13 } 14 } 15 public static void end() { 16 System.out.println("end monitor..."); 17 MethodPerformace mp = performaceRecord.get(); 18 mp.printPerformace(); 19 } 20 }
从性能监视类可以看出,里面用到了一个记录性能监视信息的辅助类。
下面是记录性能监视信息的类:
1 package com.baobaotao.proxy; 2 3 public class MethodPerformace { 4 private long begin; 5 private long end; 6 private String serviceMethod; 7 public MethodPerformace(String serviceMethod){ 8 reset(serviceMethod); 9 } 10 public void printPerformace(){ 11 end = System.currentTimeMillis(); 12 long elapse = end - begin; 13 System.out.println(serviceMethod+"花费"+elapse+"毫秒。"); 14 } 15 public void reset(String serviceMethod){ 16 this.serviceMethod = serviceMethod; 17 this.begin = System.currentTimeMillis(); 18 } 19 }
1 package com.baobaotao.proxy; 2 3 public class TestForumService { 4 public static void main(String[] args) { 5 6 ForumService forumService = new ForumServiceImpl(); 7 forumService.removeForum(10); 8 forumService.removeTopic(1012); 9 } 10 }
begin monitor... 模拟删除Topic记录:1012 end monitor... com.baobaotao.proxy.ForumServiceImpl.removeTopic花费20毫秒。
首先,把目标类的重复代码(
可增强的代码
)抽离出来,变成下面这样:
1 package com.baobaotao.proxy; 2 3 public class ForumServiceImpl implements ForumService { 4 5 public void removeTopic(int topicId) { 6 // PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeTopic"); 7 System.out.println("模拟删除Topic记录:"+topicId); 8 try { 9 Thread.currentThread().sleep(20); 10 } catch (Exception e) { 11 throw new RuntimeException(e); 12 } 13 // PerformanceMonitor.end(); 14 } 15 16 public void removeForum(int forumId) { 17 // PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeForum"); 18 System.out.println("模拟删除Forum记录:"+forumId); 19 try { 20 Thread.currentThread().sleep(40); 21 } catch (Exception e) { 22 throw new RuntimeException(e); 23 } 24 // PerformanceMonitor.end(); 25 } 26 }
我把重复代码抽离掉了。
1 package com.baobaotao.proxy; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 /**实现InvocationHandler*/ 6 public class PerformaceHandler implements InvocationHandler { 7 private Object target; 8 public PerformaceHandler(Object target){ 9 this.target = target; 10 } 11 /**重写invoke方法 12 * @param proxy 最终生成的代理实例(一般不会用到) 13 * @param method 是原始目标类(即,被代理类)的某个方法(比如removeForum),通过它,利用反射,来调用目标类的该方法。 14 * @param args 是method方法需要的一组入参。 15 * */ 16 public Object invoke(Object proxy, Method method, Object[] args) 17 throws Throwable { 18 /**只要给了target类和要调用的method方法,就可以用下面的语句给begin传入具体的方法名*/ 19 PerformanceMonitor.begin(target.getClass().getName()+"."+ method.getName()); 20 //用method方法使用反射技术调用目标类的具体方法。 21 Object obj = method.invoke(target, args); 22 PerformanceMonitor.end(); 23 return obj; 24 } 25 }
上面代码就很好的把抽离的代码( 增强 )与目标类的具体方法( 连接点 )交织了起来。
1 package com.baobaotao.proxy; 2 3 import java.lang.reflect.Proxy; 4 5 public class TestForumService { 6 public static void main(String[] args) { 7 // 使用JDK动态代理 8 ForumService target = new ForumServiceImpl(); 9 //新建PerformaceHandler对象,具体的交织过程已经在PerformaceHandler类中加了注释解读。 10 PerformaceHandler handler = new PerformaceHandler(target); 11 /**下面这一行,生成代理,有三个参数: 12 * 第一个参数:目标类的类加载器; 13 * 第二个参数:目标类的所有接口; 14 * 第三个参数:handler; 15 * */ 16 ForumService proxy = (ForumService) Proxy.newProxyInstance(target.getClass().getClassLoader(), 17 target.getClass().getInterfaces(), handler); 18 proxy.removeForum(10); 19 proxy.removeTopic(1012); 20 21 } 22 }
在 PerformaceHandler类中,我们是需要method参数的。而上面第16行生成代理的方法中的三个参数,足以用来一一遍历目标类的每一个方法,然后生成的代理类 proxy成功的为目标类的每一个方法( removeForum、 removeTopic )加入了增强代码。整个过程的时序图如下:
使用CGLib改造:JDK代理是基于接口的,而现实中很多业务程序都不是基于接口定义方法的,这就要使用CGLib来完成AOP贡功能了。 CGLib采用底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑:
1 package com.baobaotao.proxy; 2 3 import java.lang.reflect.Method; 4 5 import net.sf.cglib.proxy.Enhancer; 6 import net.sf.cglib.proxy.MethodInterceptor; 7 import net.sf.cglib.proxy.MethodProxy; 8 9 public class CglibProxy implements MethodInterceptor { 10 private Enhancer enhancer = new Enhancer(); 11 12 public Object getProxy(Class clazz) { 13 enhancer.setSuperclass(clazz);//clazz就是原目标类,即父类 14 enhancer.setCallback(this); 15 return enhancer.create();//通过字节码技术动态生成子类,并创建子类的实例。 16 } 17 public Object intercept(Object obj, Method method, Object[] args, 18 MethodProxy proxy) throws Throwable {//拦截父类所有方法的调用 19 PerformanceMonitor.begin(obj.getClass().getName()+"."+method.getName()); 20 Object result=proxy.invokeSuper(obj, args);//由proxy代理类调用父类的方法; 21 PerformanceMonitor.end(); 22 return result; 23 } 24 }
下面是测试:
1 package com.baobaotao.proxy; 2 3 4 public class TestForumService { 5 public static void main(String[] args) { 6 7 //使用CGLib动态代理 8 CglibProxy proxy = new CglibProxy(); 9 //动态生成子类的方式创建代理类,它拥有原目标类的方法和增强代码 10 ForumService forumService = (ForumService)proxy.getProxy(ForumServiceImpl.class); 11 forumService.removeForum(10); 12 forumService.removeTopic(1023); 13 14 } 15 }
打印结果是一样的。