Spring AOP基础-CGLib动态代理
使用JDK创建代理有一个限制,即他只能为接口创建代理实例,这一点我们可以从Proxy的接口newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)的方法签名中就看得很清楚:第二个参数interfaces就是需要代理实例实现的接口列表。对于没有通过接口定义业务方法的类,如何动态创建代理实例呢?JDK的代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这个空缺。
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。下面,我们采用CGLib技术,编写一个可以为任何类创建织入性能监视的横切逻辑代理对象的代理创建器,代码如下:
同JDK代理一样,首先需要一个工具类来监视方法性能的类 MethodPerformance.class
package com.cglib; public class MethodPerformance { private long begin; private long end; private String serviceMethod; public MethodPerformance(String serviceMethod){ this.serviceMethod = serviceMethod; this.begin = System.currentTimeMillis(); } public void printPerformance(){ end = System.currentTimeMillis(); long elapse = end - begin; System.out.println(serviceMethod + "花费" + elapse + "毫秒"); } }
然后将性能监视的代码安置在PerformanceHandler中:
package com.cglib; public class PerformanceMonitor { private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>(); public static void begin(String method){ System.out.println("begin monitor……"); MethodPerformance mp = new MethodPerformance(method); performanceRecord.set(mp); } public static void end(){ System.out.println("end monitor……"); MethodPerformance mp = performanceRecord.get(); mp.printPerformance(); } }
业务类是ForumServiceImpl,不同于JDK代理,此处的业务方法是没有继承任何接口的
package com.cglib; public class ForumServiceImpl { @SuppressWarnings("static-access") public void removeForum(int forumId) { System.out.println("模拟删除Forum记录:" + forumId); try { Thread.currentThread().sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } @SuppressWarnings("static-access") public void removeTopic(int topicId) { System.out.println("模拟删除Topic记录:" + topicId); try { Thread.currentThread().sleep(40); } catch (InterruptedException e) { e.printStackTrace(); } } }
然后我们采用CGLib技术,编写一个的代理创建器如下:
package com.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CGLibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { PerformanceMonitor.begin(obj.getClass().getName() + "." + method.getName()); Object result = proxy.invokeSuper(obj, args); PerformanceMonitor.end(); return result; } }
最后通过一个测试类进行测试:
package com.cglib; public class TestForumService { public static void main(String[] args) { CGLibProxy proxy = new CGLibProxy(); ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class); forumService.removeForum(10); forumService.removeTopic(1023); } }
测试结果如下:
begin monitor……
模拟删除Forum记录:10
end monitor……
com.cglib.ForumServiceImpl$$EnhancerByCGLIB$$b80af08a.removeForum花费31毫秒
begin monitor……
模拟删除Topic记录:1023
end monitor……
com.cglib.ForumServiceImpl$$EnhancerByCGLIB$$b80af08a.removeTopic花费47毫秒
结果输出和使用JDK代理是基本一致的,但是在类名上有区别,这是因为实际运行的是CGLib为ForumServiceceImpl创建的子类,而上面的类名就是创建的子类的名字。
JDK 和 CGLib比较:
JDK动态代理所创建的代理对象,在JDK1.3下,性能还差强人意。虽然在高版本的JDK中,动态代理对象的性能得到很到的提高,但是CGLib所创建的动态代理对象的性能依旧比JDK的所创建的代理对象的性能高不少(大概10倍)。但CGLib在创建代理对象时所花费的时间却比JDK动态代理多(大概8倍),所以对于Singleton的代理对象或者具有实例池的代理,因为无须频繁的创建代理对象,所以比较适合用CGLib动态代理技术,反之适用JDK动态代理技术。但是,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final方法进行代理,另外如果类没有实现任何接口,则只能选择CGLib进行动态代理。