问题
系统整合了shiro框架后,发现方法本体执行一次,aop执行两次!
经过研究,是因为系统中有两个代理创建器,对一个通知器(通知器包含切点和通知)生成两个代理类导致的。以下是研究过程。
基本配置
spring 基本配置如下
<context:component-scan base-package="testmaven.service"></context:component-scan>
<bean id="myinterceptor" class="testmaven.interceptor.MyInterceptor"></bean>
<aop:config proxy-target-class="false">
<aop:pointcut expression="execution(* testmaven.service.*.*(..))" id="mypointcut"/>
<aop:advisor advice-ref="myinterceptor" pointcut-ref="mypointcut"/>
</aop:config>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
因为proxy-target-class=“false”,采用的是JDK代理,所以,打算将代理类生成到磁盘上,一探究竟。
为什么aop执行两次?
设置jvm参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
生成代理类后(com.sun.proxy、org.springframework.core文件夹需要手动创建),反编译观察
发现
$Proxy13与$Proxy14都是MyService的代理类!!!
观察sayHello方法
m3方法就是MyService的sayHello方法
此处的h是什么呢?是JDKDynamicAopProxy,它实现了InvocationHandler
$Proxy14与$Proxy13基本一样,这里不再展示。
通过debug查看线程栈再次验证了上述论证,线程中依次执行了Proxy14、Proxy13的sayHello方法
至此aop执行两次的原因找到了,因为产生了两个代理。
为什么方法本体执行了一次?
按照常理,有两个代理,方法本体会执行两次,为什么本体却只执行了一次呢?
这是因为
Proxy13代理了MyService,而Proxy14又代理了Proxy13!
这也解释了为什么proxy14比proxy13多实现一个serializable接口,因为java.lang.reflect.Proxy类实现了serializable接口,所以proxy14代理proxy13时,就需要实现serializable接口了。
等同于代码
proxy14.java
public void sayHello(){
super.h.invoke(target,args);//target就是$Proxy13
}
proxy13.java
public void sayHello(){
super.h.invoke(target,args);//target就是MyServiceImpl
}
JDKAopDynamicProxy.java
public void invoke(){
System.out.println("method before.....");
//第一次,打印一次method before,此时target是$Proxy13,所以执行$Proxy13的sayHello
//第二次,打印一次method before,此时target是MyserviceImpl,执行MyserviceImpl的sayHello
method.invoke(target,args);
}
proxy14的target是proxy13
Proxy13代理了MyService
综上所述,aop执行两次,方法本体执行一次
解决办法
去掉DefaultAdvisorAutoProxyCreator,让系统中,只存在一个代理创建器。修改后
<context:component-scan base-package="testmaven.service"></context:component-scan>
<bean id="myinterceptor" class="testmaven.interceptor.MyInterceptor"></bean>
<aop:config proxy-target-class="false">
<aop:pointcut expression="execution(* testmaven.service.*.*(..))" id="mypointcut"/>
<aop:advisor advice-ref="myinterceptor" pointcut-ref="mypointcut"/>
</aop:config>
更深层次的原因
一个切面产生两个代理更深层次的原因是因为有两个代理创建器,AspectJAwareAdvisorAutoProxyCreator
、DefaultAdvisorAutoProxyCreator
<aop:config/>
产生了AspectJAwareAdvisorAutoProxyCreator
<aop:config/>
由AopNamespaceHandler处理
public class AopNamespaceHandler extends NamespaceHandlerSupport {
/**
* Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
* '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
* and '{@code scoped-proxy}' tags.
*/
@Override
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
aop:config
是由ConfigBeanDefinitionParser处理的,一路追踪,最终到了
public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
}
可以看到<aop:config/>
创建了AspectJAwareAdvisorAutoProxyCreator
!
再加上配置文件中的DefaultAdvisorAutoProxyCreator
,就存在两个代理创建器,代理创建器会扫描配置文件中的advisor创建代理。所以对同一个切面,创建了两个代理。
总结
aop执行多次的原因是配置了多个代理创建器,多个代理创建器,产生了多个代理,代理2代理了代理1,代理1代理了本体,所以就产生了aop执行两次,本体方法执行一次的现象。
所以,系统中最好只有一个代理创建器,避免同一个通知器被创建多个代理的问题。