spring aop 執行兩次
問題
系統整合了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執行兩次,本體方法執行一次的現象。
所以,系統中最好只有一個代理建立器,避免同一個通知器被建立多個代理的問題。