Spring AOP使用动态代理技术在运行期织入增强的代码,两种代理机制包括:一是基于JDK的动态代理,另一种是基于CGLib的动态代理。之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理。
1、带有横切逻辑的实例
ForumService:包含性能监视横切代码
package com.yyq.proxy; public interface ForumService { void removeTopic(int topicId); void removeForum(int forumId); }
ForumServiceImpl:实现类
package com.yyq.proxy; public class ForumServiceImpl implements ForumService { @Override public void removeTopic(int topicId) { PerformanceMonitor.begin("com.yyq.proxy.ForumServiceImpl.removeTopic"); System.out.println("模拟删除Topic记录:" + topicId); try { Thread.currentThread().sleep(20); } catch (Exception e) { throw new RuntimeException(e); } PerformanceMonitor.end(); } @Override public void removeForum(int forumId) { PerformanceMonitor.begin("com.yyq.proxy.ForumServiceImpl.removeForum"); System.out.println("模拟删除Forum记录:" + forumId); try { Thread.currentThread().sleep(40); }catch (Exception e){ throw new RuntimeException(e); } PerformanceMonitor.end(); } }
PerformanceMonitor:性能监视的实现类
package com.yyq.proxy; 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(); } }
MethodPerformance:记录性能监视信息
package com.yyq.proxy; 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 + "毫秒。"); } }
TestProxy.testForumService测试方法:
@Test public void testForumService(){ ForumService forumService = new ForumServiceImpl(); forumService.removeForum(10); forumService.removeTopic(1012); }
输出结果:
begin monitor...
模拟删除Forum记录:10
end monitor...
com.yyq.proxy.ForumServiceImpl.removeForum花费40毫秒。
begin monitor...
模拟删除Topic记录:1012
end monitor...
com.yyq.proxy.ForumServiceImpl.removeTopic花费20毫秒。
存在的问题:当某个方法需要进行性能监视,就必须调整方法代码,在方法体前后分别添加上开启性能监视和结束性能监视的代码。这些非业务逻辑的性能监视代码破坏了ForumServiceImpl业务逻辑的纯粹性。所以需要通过代理的方式,将业务类方法中开启和结束性能监视的这些横切代码从业务类中完全移除,并通过JDK动态代理技术或CGLib动态代理技术将横切代码动态织入到目标方法的相应位置。
2、JDK 动态代理
JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。而Proxy利用invocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
ForumServiceImpl:移除性能监视横切代码
package com.yyq.proxy; public class ForumServiceImpl implements ForumService { public void removeTopic(int topicId) { System.out.println("模拟删除Topic记录:" + topicId); try { Thread.currentThread().sleep(20); } catch (Exception e) { throw new RuntimeException(e); } PerformanceMonitor.end(); } public void removeForum(int forumId) { System.out.println("模拟删除Forum记录:" + forumId); try { Thread.currentThread().sleep(40); }catch (Exception e){ throw new RuntimeException(e); } PerformanceMonitor.end(); } }
PerformanceHandler:性能监视横切代码
package com.yyq.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class PerformanceHandler implements InvocationHandler { private Object target; public PerformanceHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName()); Object obj = method.invoke(target, args); PerformanceMonitor.end(); return obj; } }
method.invoke()语句通过Java反射机制间接调用目标对象的方法,这样InvocationHandler的invoke()方法就将横切逻辑点和业务类方法的业务逻辑代码编织到一起了。
TestProxy.testForumService2:创建代理实例
@Test public void testForumService2(){ ForumService target = new ForumServiceImpl(); PerformanceHandler handler = new PerformanceHandler(target); ForumService proxy = (ForumService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),handler); proxy.removeTopic(10); proxy.removeForum(1012); }
通过Proxy的newProxyInstance()静态方法为编织了业务类逻辑和性能监视逻辑的handler创建一个符合ForumService接口的代理实例。该方法的第一个入参为类加载器;第二个入参为创建代理实例所需要实现的一组接口;第三个参数是整合了业务逻辑和横切逻辑的编织器对象。
输出结果:
begin monitor...
模拟删除Topic记录:10
end monitor...
com.yyq.proxy.ForumServiceImpl.removeTopic花费22毫秒。
begin monitor...
模拟删除Forum记录:1012
end monitor...
com.yyq.proxy.ForumServiceImpl.removeForum花费41毫秒。
3、CGLib动态代理
使用JDK创建代理有一个限制,即它只能为接口创建代理实例。而CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。
CglibProxy:为任何类创建织入性能监视横切逻辑代理对象的代理创建器
package com.yyq.proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; 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; } }
intercept(Object obj, Method method, Object[] args, MethodProxy proxy)是CGLib定义的Interceptor接口的方法,它拦截所有目标类方法的调用,obj表示目标类的实例;method为目标类方法的反射对象;args为方法的动态入参;而proxy为代理类实例。
TestProxy.testForumService3:测试Cglib创建的代理类
@Test public void testForumService3(){ CglibProxy proxy = new CglibProxy(); ForumServiceImpl forumService = (ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class); forumService.removeForum(10); forumService.removeTopic(1012); }
输出结果:
begin monitor...
模拟删除Forum记录:10
end monitor...
com.yyq.proxy.ForumServiceImpl$$EnhancerByCGLIB$$70ce1267.removeForum花费80毫秒。
begin monitor...
模拟删除Topic记录:1012
end monitor...
com.yyq.proxy.ForumServiceImpl$$EnhancerByCGLIB$$70ce1267.removeTopic花费20毫秒。
4、代理知识小结
Spring AOP的底层就是通过使用JDK动态代理或CGLib动态代理技术为目标Bean织入横切逻辑。但是以上实现方式存在三个明显需要改进的地方:
1)目标类的所有方法都添加了性能监视横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加横切逻辑;
2)我们通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码;
3)我们手工编写代码实例的创建过程,为不同类创建代理时,需要分别编写相应的程序代码,无法做到通用。
Spring AOP的主要工作围绕以上三点展开:Spring AOP通过Pointcut(切点)指定在哪些类的哪些方法上织入横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等)。此外,Spring通过Advisor(切面)将Pointcut和Advice两者组装起来。有了Advisor的信息,Spring就可以利用JDK或CGLib的动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了。