最近遇到一個(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ì)象

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)注,很容易埋一些坑在里面。