上一篇分析了兩種代理的大致原理堡牡,spring框架內的aop就是使用的這兩種代理模式则涯。spring在默認情況下可以根據被代理類是否實現接口自動切換代理方式复局,實現了接口使用jdk代理,沒實現接口使用cglib粟判。
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
/**
* Determine whether the supplied {@link AdvisedSupport} has only the
* {@link org.springframework.aop.SpringProxy} interface specified
* (or no proxy interfaces specified at all).
*/
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
}
然后如果我們在配置文件設置了<aop:aspectj-autoproxy proxy-target-class="true"/>后亿昏,“通常,指定{@code proxyTargetClass}來強制執(zhí)行CGLIB代理档礁,或指定一個或多個接口以使用JDK動態(tài)代理角钩。” 這是spring的注釋,意思是代理被強制指定cglib呻澜。
上面這段代碼發(fā)生在程序啟動時递礼,bean注入到DefaultListableBeanFactory的beanDefinitionNames里面,如果當前被注入的bean并沒有需要代理的標簽等羹幸,比如自定義aop脊髓,聲明式事務等,是不會走到這里的栅受,直接注入的就是相應的類将硝,而需要代理的會被包裝成代理注入進去恭朗。
然后接下來還是啟動過程中,會執(zhí)行bean的初始化的托管注入依疼,被@Autowiredd的bean會走一系列的步驟尋找出實例注入到當前初始化的bean痰腮。這一系列的動作鏈路很長,并且bean加載順序不同律罢,步驟略有不同诽嘉,debug出一種:
DefaultListableBeanFactory.resolveDependency()--DefaultListableBeanFactory.doResolveDependency()--DefaultListableBeanFactory.findAutowireCandidates()--BeanFactoryUtils.beanNamesForTypeIncludingAncestors()--
DefaultListableBeanFactory.getBeanNamesForType()--DefaultListableBeanFactory.doGetBeanNamesForType()--AbstractBeanFactory.isTypeMatch()--ResolvableType.isInstance()--ResolvableType.static ResolvableType forRawClass()--
ResolvableType.static ResolvableType forRawClass {}isAssignableFrom()--ResolvableType.getRawClass()--ClassUtils.isAssignable() retrun true or false。
ClassUtils.isAssignable()方法是native的方法弟翘,用來判斷是否可以轉型虫腋。
這樣分兩種情況:
1.proxy-target-class=false
一個沒實現接口的類,spring動態(tài)選擇使用了cglib代理稀余,被注入時TypeMatch時比較這個類和代理后他的子類悦冀,可以注入。
一個實現了接口的類睛琳,spring動態(tài)選擇使用了jdk代理盒蟆,當使用接口(如xxxService)注入時,TypeMatch時比較接口類和代理后的Proxy子類并實現了這個接口xxxService的類师骗,可以注入历等。
一個實現了接口的類,spring動態(tài)選擇使用了jdk代理辟癌,當使用實現類(如xxxServiceImpl)注入時寒屯,TypeMatch時比較實現類與代理后的Proxy子類并實現了這個接口xxxService的類,這時候是不通過的黍少,因為他們既不是父類子類的關系寡夹,也不是實現的關系,這會導致啟動失敗厂置,無法注入的錯誤菩掏。但是一般不會有人這么做,這是在我們項目里發(fā)現的做法昵济,導致不能使用動態(tài)選擇代理智绸,只能將proxy-target-class=true解決。
2.proxy-target-class=true
一個沒實現接口的類访忿,spring強制使用了cglib代理瞧栗,被注入時TypeMatch時比較這個類和代理后他的子類,可以注入醉顽。
一個實現了接口的類沼溜,spring強制使用了cglib代理平挑,當使用接口(如xxxService)注入時游添,TypeMatch時比較接口類和代理后的xxxServiceImpl類型的子類系草,可以注入。
一個實現了接口的類唆涝,spring強制使用了cglib代理找都,當使用實現類(如xxxServiceImpl)注入時,TypeMatch時比較xxxServiceImpl和代理后的xxxServiceImpl類型的子類廊酣,可以注入能耻。
所以配置文件中如何選擇proxy-target-class屬性,可根據具體代碼決定亡驰。
實際項目中晓猛,發(fā)現在配置文件applicationContext.xml中加了proxy-target-class=true后,實現類注入依然注入失敗凡辱,排查發(fā)現配置文件的component-scan配置有問題戒职。正常我們項目有兩個主要的配置文件,applicationContext.xml的父容器透乾,xxx-servlet.xml的mvc子容器配置文件洪燥。component-scan配置正確的方式是父容器掃描除了controller外的bean,然后mvc子容器僅掃描controller的bean乳乌。
父容器:
<context:component-scan base-package="com.storm">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
mvc子容器:
<context:component-scan base-package="com.storm" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
這樣是正確的配置捧韵,也可以制定具體的包進行掃描,例如:
<context:component-scan base-package="com.storm.controller" />
我們項目在mvc的配置文件沒有加use-default-filters="false"汉操,如果不加這個配置再来,他會默認掃描全部4種注解,導致又一次的掃描到service注解磷瘤,serivce又被初始化了一遍其弊,并覆蓋之前初始化好的bean,而這個子容器的proxy-target-class沒有配置膀斋,為默認的false梭伐,所以導致了初始化失敗。