Spring二次代理問題排查

最近遇到一個(gè)Spring注入失敗的問題。起因是需要在一個(gè)Controller ClassB里注入另一個(gè)Controller ClassA(一個(gè)令人蛋疼的原因),啟動(dòng)的時(shí)候報(bào)異常,提示需要的類型是ClassA,找到的Bean的實(shí)際類型是com.sun.proxy.$Proxy。代碼里有一個(gè)用來打日志Aop的切面涉及到了ClassA。但是ClassA明明沒有實(shí)現(xiàn)任何接口啊,proxy對(duì)象也應(yīng)該是使用CGlib生成的,類型應(yīng)該是ClassA$$EnhancerByCGLIB才對(duì)。
把注入Controller的類型改為Object,然后加了斷點(diǎn)Debug看了下bean內(nèi)容。如下圖所示,可以看出真正的原始Controller被代理了兩次。先是使用CGlib生成的FirstController$$EnhancerBySpringCGLIB類型的代理對(duì)象,然后使用JDK動(dòng)態(tài)代理使用這個(gè)代理對(duì)象做為target又生成了一個(gè)com.sun.proxy.$Proxy類型的代理對(duì)象

Proxy對(duì)象的數(shù)據(jù)信息

google了一下,看到一篇文章和我遇到的問題很相似:spring二次代理的問題。先說一下問題產(chǎn)生的根本原因:spring上下文里的的BeanPostProcessor列表有多個(gè)AbstractAdvisorAutoProxyCreator。spring的動(dòng)態(tài)代理的原理是在bean生命周期的BeanPostProcessor拓展點(diǎn)對(duì)bean進(jìn)行檢查是否符合某一個(gè)AOP切面的規(guī)則,如果需要代理的話,將這個(gè)bean替換為代理對(duì)象。AbstractAdvisorAutoProxyCreator就是用來生成代理
對(duì)象的BeanPostProcessor,它一共有下面四個(gè)實(shí)現(xiàn),除了InfrastructureAdvisorAutoProxyCreator,其他的ProxyCreator都能夠解析用戶通過Advisor類型的Bean定義的切面

DefaultAdvisorAutoProxyCreator //基礎(chǔ)的ProxyCreator 
AnnotationAwareAspectJAutoProxyCreator //能夠解析AspectJ注解的ProxyCreator
AspectJAwareAdvisorAutoProxyCreator //能夠解析AspectJ規(guī)則的ProxyCreator
InfrastructureAdvisorAutoProxyCreator //只解析Spring本身基礎(chǔ)設(shè)施里的切面,忽略用戶定義的切面

BeanPostProcessor的機(jī)制是從Spring管理的bean中找到所有實(shí)現(xiàn)了BeanPostProcessor接口的類,將其注冊(cè)到beanPostProcessors中,bean會(huì)被beanPostProcessors中的每一個(gè)Processor處理,也就是說在存在多個(gè)ProxyCreator的情況下,bean被之前的ProxyCreator處理以后已經(jīng)變成了一個(gè)代理對(duì)象,仍然會(huì)被下一個(gè)ProxyCreator繼續(xù)處理,如果這個(gè)代理對(duì)象仍然符合某一個(gè)AOP切面的規(guī)則,就會(huì)對(duì)這個(gè)代理對(duì)象在產(chǎn)生一個(gè)代理對(duì)象。

項(xiàng)目中出現(xiàn)問題的代碼類似于下面這樣,通過Advisor定義了一個(gè)切面,規(guī)則是所有q.g.controller下的類生效。即使用了aop:aspectj-autoproxy(相當(dāng)于注冊(cè)了一個(gè)DefaultAdvisorAutoProxyCreator),又注冊(cè)了一個(gè)DefaultAdvisorAutoProxyCreator。這時(shí)beanPostProcessors就有了就有了兩個(gè)ProxyCreator,首先第一個(gè)ProxyCreator對(duì)controller進(jìn)行處理,因?yàn)閏ontroller沒有實(shí)現(xiàn)任何接口,所以采用CGlib生成了代理對(duì)象,代理對(duì)象的類型是FirstController$$EnhancerBySpringCGLIB,同時(shí)實(shí)現(xiàn)了三個(gè)接口:org.springframework.aop.SpringProxy
org.springframework.aop.framework.Advised
org.springframework.cglib.proxy.Factory
然后第二個(gè)ProxyCreator對(duì)這個(gè)代理對(duì)象進(jìn)行處理,該代理對(duì)象的類的package也是q.g.controller,仍然符合切面的規(guī)則。因?yàn)樵摯韺?duì)象實(shí)現(xiàn)了三個(gè)接口,所以就使用JDK基于接口的動(dòng)態(tài)代理生成代理對(duì)象。代理對(duì)象的類型是$Proxy,package是com.sun.proxy,不再符合切面的規(guī)則,即使后面仍然有更多的ProxyCreator,也不會(huì)再基于當(dāng)前的代理對(duì)象生成一個(gè)新的代理對(duì)象

    <aop:aspectj-autoproxy/>
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
    <bean id="firstAdvice" class="q.g.aop.FirstAdvice"></bean>
    <bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice">
            <ref bean="firstAdvice" />
        </property>
        <property name="patterns">
            <list>
                <value>q\.g\.controller\..*</value>
            </list>
        </property>
    </bean>

如果將配置改為下面這樣,強(qiáng)制使用CGlib生成代理對(duì)象,有N個(gè)ProxyCreator就會(huì)產(chǎn)生N次代理

    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
        <property name="proxyTargetClass" value="true"/>
    </bean>
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
        <property name="proxyTargetClass" value="true"/>
    </bean>
    <bean id="firstAdvice" class="q.g.aop.FirstAdvice"></bean>
    <bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice">
            <ref bean="firstAdvice" />
        </property>
        <property name="patterns">
            <list>
                <value>q\.g\.controller\..*</value>
            </list>
        </property>
    </bean>
總結(jié)

出現(xiàn)多次代理的條件:

  • 有多個(gè)ProxyCreator
  • 生成的代理對(duì)象仍然符合切面規(guī)則

不要使用多種AOP配置的方式和定義切面時(shí)范圍盡量精確可以很大程度避免發(fā)生這個(gè)問題。

另外,雜亂的spring配置文件真是排查這類問題的大敵。不得不吐槽公司項(xiàng)目中的spring 配置文件真的又多又亂,沒有一個(gè)統(tǒng)一的規(guī)則,大家在改動(dòng)時(shí)使用的配置方式、修改的配置文件都不一樣,再加上Spring mvc里servlet context 和root context的關(guān)系大家一般也不會(huì)去關(guān)注,很容易埋一些坑在里面。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 什么是Spring Spring是一個(gè)開源的Java EE開發(fā)框架。Spring框架的核心功能可以應(yīng)用在任何Jav...
    jemmm閱讀 16,771評(píng)論 1 133
  • 1.什么是Spring框架? Spring是一個(gè)輕量級(jí)的java開源框架,為了解決企業(yè)級(jí)應(yīng)用開發(fā)的復(fù)雜性創(chuàng)建的ja...
    gskobe0811閱讀 567評(píng)論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,545評(píng)論 19 139
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,792評(píng)論 11 349
  • 與小人相處的黃金五步法 小人很讓人討厭,比如愛打小報(bào)告的人,因?yàn)樗麜?huì)把很多你的臭事糟糕事負(fù)面事捅到領(lǐng)導(dǎo)那里,讓你難...
    創(chuàng)一學(xué)習(xí)吧閱讀 429評(píng)論 0 1

友情鏈接更多精彩內(nèi)容