最近一個朋友使用javamelody時遇到一個二次代理的問題,即一個Bean被代理了兩次。
我還原了一下問題,並簡化出一個工程方便大家觀察。可以下載附件代碼還原場景。
代碼如下:
1、接口及目標類
- package com.sishuok.proxy;
- public interface Interface {
- public void sayHello();
- }
- package com.sishuok.proxy;
- public class Target implements Interface {
- public void sayHello() {
- System.out.println("===hello");
- }
- }
2.1、spring-config.xml配置:
- <bean id="myBean" class="com.sishuok.proxy.MyBean">
- <property name="target" ref="target"/>
- </bean>
- <bean id="target" class="com.sishuok.proxy.Target"/>
- <bean id="myAspect" class="com.sishuok.proxy.aspect.MyAspect"/>
- <aop:config proxy-target-class="true">
- <aop:aspect ref="myAspect">
- <aop:before method="before" pointcut="execution(* com.sishuok.proxy.*.*(..))"/>
- </aop:aspect>
- </aop:config>
aop:config proxy-target-class="true"走CGLIB類代理,而不是JDK動態代理。
2.2、配置文件other-config.xml
- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
問題分析:
1、首先spring-config.xml配置文件的<aop:config>會交給AopNamespaceHandler處理:
- http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
- registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
2、aop:config委託給ConfigBeanDefinitionParser處理,並通過如下代碼註冊自動代理創建器:
- configureAutoProxyCreator(parserContext, element);
- private void configureAutoProxyCreator(ParserContext parserContext, Element element) {
- AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
- }
最終會委託給如下代碼(中間過程省略,都是委託):
- public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
- return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
- }
- private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) {
- Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
- if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
- BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
- if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
- int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
- int requiredPriority = findPriorityForClass(cls);
- if (currentPriority < requiredPriority) {
- apcDefinition.setBeanClassName(cls.getName());
- }
- }
- return null;
- }
- RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
- beanDefinition.setSource(source);
- beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
- beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
- return beanDefinition;
- }
即最終會創建一個AspectJAwareAdvisorAutoProxyCreator,如上代碼意思就是:如果當前容器中已經有一個AUTO_PROXY_CREATOR_BEAN_NAME,那麼根據實際情況修改配置,否則添加一個(也就是說一個容器中不管有多少個aop:config也最多隻添加一個AspectJAwareAdvisorAutoProxyCreator)
2、接着會添加other-config.xml的DefaultAdvisorAutoProxyCreator,即又添加了一個自動代理創建器;
注意 :這兩個AutoProxyCreator都是BeanPostProcessor,具體參考如下兩篇文章,此處就不詳述了:
所以問題就出現了(以下順序默認應該看成無序,可以修改order屬性來指定順序,但沒有作用):
- AspectJAwareAdvisorAutoProxyCreator會創建一個代理(因爲<aop:config proxy-target-class="true">),這個代理是CGLIB代理;
- DefaultAdvisorAutoProxyCreator會對代理對象再創建代理,但是因爲沒有告訴它代理類,所以默認代理接口,即代理是JDK動態代理;
即雖然AspectJAwareAdvisorAutoProxyCreator創建了類代理,但DefaultAdvisorAutoProxyCreator還是創建了JDK動態代理(接口)。
解決辦法:
1、DefaultAdvisorAutoProxyCreator也是cglib代理:
- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
- <property name="proxyTargetClass" value="true"/>
- </bean>
2、全部使用<aop:config>,不要自己去定義自己的AutoProxyCreator,這也是推薦的方式,因爲這樣一個容器永遠只有一個AutoProxyCreator。
如何判斷是二次代理
觀察異常:
Caused by: java.lang.IllegalStateException: Cannot convert value of type [$Proxy0 implementing org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised,org.springframework.cglib.proxy.Factory,com.sishuok.proxy.Interface] to required type [com.sishuok.proxy.Target] for property 'target': no matching editors or conversion strategy found
- 見到$Proxy開頭的類,基本上可以確定是JDK動態代理
- 此處可以看到$Proxy0 實現了 一堆接口,能看到一個org.springframework.cglib.proxy.Factory,從這個已經能判斷出其是二次代理了,即當前的JDK動態代理代理了CGLIB代理。
- 如果見到如輸出的class是com.sishuok.proxy.Target$$EnhancerByCGLIB$$12c22b67,那就是CGLIB代理了。
總結
- 首選如<aop:config>,而不是自己定義如×××AutoProxyCreator,而且使用<aop:config>方式能更好的描述切面。
- 觀察類是$Proxy…… 還是 ……$$EnhancerByCGLIB$$……,來判斷是JDK動態代理還是CGLIB代理。
- 通過觀察$Proxy的實現中是否包含org.springframework.cglib.proxy.Factory來判斷是否是二次代理。
- 通過《Spring事務不起作用 問題彙總》 中介紹的方式查看是否創建了代理。
分析完畢。