1. 程式人生 > >spring aop 執行兩次

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>

更深層次的原因

一個切面產生兩個代理更深層次的原因是因為有兩個代理建立器,AspectJAwareAdvisorAutoProxyCreatorDefaultAdvisorAutoProxyCreator
<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執行兩次,本體方法執行一次的現象。
所以,系統中最好只有一個代理建立器,避免同一個通知器被建立多個代理的問題。