假设我们有如下所示的目标工具类定义。当然,这样的类定义可以映射到系统中的任何可能的业务工具。
public class NestableInvocationBO { public void method1() { method2(); System.out.println("method1 executed!"); } public void method2() { System.out.println("method2 executed."); }}复制代码
该类定义中须要我们关注的是它的某个给方法会调用同一工具上定义的其他方法。这常日是比较常见的。在NestableInvocationBO类中,method1方法调用了同一工具的method2方法。
现在,我们要利用Spring AOP拦截该类定义的method1和method2方法,比如加入一个大略的性能检测逻辑。那么可以定义一个PerformanceTraceAspect,代码如下:

@Aspectpublic class PerformanceTraceAspect { private final Log logger = LogFactory = LogFactory.getLog(PerformanceTraceAspect.class); @Pointcut("execution(public void .method1())") public void method1(){} @Pointcut("execution(public void .method2())") public void method2(){} @Pointcut("method1() || method2()") public void compositePointcut(){} @Around("compositePointcut()") public Object performanceTrace(ProceedingJoinPoint joinpoint) throws Throwable { StopWatch watch = new StopWatch(); try { watch.start(); return joinpoint.proceed(); } finally { watch.stop(); if (logger.isInfoEnabled()) { logger.info("PT in method[" +joinpoint.getSignature().getName()+ "]>>"+watch.toString()); } } }}复制代码
Around Advice定义会拦截compositePointcut()所指定的JoinPoint,即method1或者method2的实行。
如果将PerformanceTraceAspect中定义的横切逻辑织入NestableInvocationBO,然后运行如下代码并查当作果:
AspectJProxyFactory weaver = new AspectJProxyFactory(new NestableInvocationBO());weaver.setProxyTargetClass(true);weaver.addAspect(PerformanceTraceAspect.class);Object proxy = weaver.getProxy();(NestableInvocationBO) proxy.method2();(NestableInvocationBO) proxy.method1();复制代码
会得到如下的输出结果:
method2 executed!701 [main] INFO ...PerformanceTraceAspect - PT in method[method2]>>0:00:00.000method2 executed!method1 executed!701 [main] INFO ...PerformanceTraceAspect - PT in method[method1]>>0:00:00.000复制代码
创造问题了没有?当我们从外部直接调用NestableInvocationBO工具的method2的时候,由于该方法署名匹配PerformanceTraceAspect中Around Advice所对应的Pointcut定义,以是,Around Advice逻辑得以实行,也便是说,PerformanceTraceAspect拦截method2的实行成功了。但是,当调用method1的时候,却只有method1方法的实行拦截成功,而method1方法内部的method2方法实行却没有被拦截,由于在输入日志中只有PT in method[method1]的信息。
缘故原由剖析这种结果的涌现,归根结底是由Spring AOP的实现机制造成的。我们知道,Spring AOP采取代理模式实现AOP,详细的横切逻辑会被添加到动态天生的代理工具中,只要调用的是目标工具的代理工具上的方法,常日就可以担保目标工具上的方法实行可以被拦截。就像NestableInvocationBO的method2方法实行一样,当我们调用代理工具上的method2的时候,目标工具的method2就会被成功拦截。
不过,代理模式的实现机制在处理方法调用的时序方面,会给利用这种机制实现的AOP产品造成一个小小的“缺憾”。我们来看一样平常的代理工具方法与目标工具方法的调用时序,如下所示:
proxy.method2 {记录方法调用开始韶光;target.method2();记录方法调用结束韶光;打算花费的韶光并记录到日志;}复制代码
在代理工具方法中,不管你如何添加横切逻辑,也不管你添加多少横切逻辑,有一点是确定的,那便是,你终归须要调用目标工具上的同一方法来实行最初所定义的方法逻辑。
如果目标工具中原始方法调用依赖于其他工具,那没问题,我们可以为目标工具注入所依赖工具的代理,并且可以担保相应Joinpoint被拦截注入横切逻辑。而一旦目标工具中的原始方法调用直接调用自身的方法的时候,也便是说,它所依赖于自身所定义的其他的方法的时候,那问题就来了,如下图所示:
在代理工具的method1方法实行经历了层层拦截器之后,终极会将调用转向目标工具上的method1,之后的调用流程全部都是走在TargetObject之上,当method1调用method2时,它调用的是TargetObject上的method2,而不是ProxyObject上的method2。要知道,针对method2的横切逻辑,只是织入到了ProxyObject上的method2方法中,以是,在method1中所调用的method2没有能够被成功拦截。
办理方法知道了缘故原由,我们就可以“对症下药”了。
当目标工具依赖于其他工具时,我们可以通过为目标工具注入依赖工具的代理,来办理相应的拦截问题。那么,当目标工具依赖于自身时,我们也可以考试测验将目标工具的代理工具公开给它,只要让目标工具调用自身代理工具上的方法,就可以办理内部调用的方法没有被拦截的问题。
Spring AOP供应了AopContext来公开当前目标工具的代理工具,我们只要在目标工具中利用AopContext.currentProxy()就可以取得当前目标工具所对应的代理工具。现在,我们重构目标工具,让它直接调用它的代理工具的相应的方法:
public class NestableInvocationBO { public void method1() { ((NestableInvocationBO)AopContext.currentProxy()).method2(); System.out.println("method1 executed!"); } public void method2() { System.out.println("method2 executed!"); }}复制代码
要使AopContext.currentProxy()生效,我们在天生目标工具的代理工具时,须要将ProxyConfig或者它的相应子类的exposeProxy属性设置为true,如下所示:
AspectJProxyFactory weaver = new AspectJProxyFactory(new NestableInvocationBO());weaver.setProxyTargetClass(true);weaver.setExposeProxy(true); //weaver.addAspect(PerformanceTraceAspect.class);Object proxy = weaver.getProxy();(NestableInvocationBO) proxy.method2();(NestableInvocationBO) proxy.method1();复制代码
现在,我们得到了想要的拦截结果
method2 executed!611 [main] INFO ...PerformanceTraceAspect - PT in method[method2]>>0:00:00.000method2 executed!611 [main] INFO ...PerformanceTraceAspect - PT in method[method2]>>0:00:00.000method1 executed!611 [main] INFO ...PerformanceTraceAspect - PT in method[method1]>>0:00:00.000复制代码
问题是办理了,但办理的不是很美观,由于我们的目标工具都直接绑定到了Spring AOP的详细API上了。以是,我们考虑重构目标工具定义。我们可以在目标工具中声明对其代理工具的依赖,而由IoC容器来帮助我们注入这个代理工具。
把稳实际上,这种情形的涌现仅仅是由于Spring AOP实现机制导致的一个小小的陷进。如果像AspectJ那样,直接将横切逻辑织入目标工具,那么代理工具和目标工具实际上就合为一体了,调用也不会涌现这样的问题。