spring二次代理的問題

最近一個朋友使用javamelody時遇到一個二次代理的問題,即一個Bean被代理了兩次。

 

我還原了一下問題,並簡化出一個工程方便大家觀察。可以下載附件代碼還原場景。

 

代碼如下:

1、接口及目標類 

Java代碼  收藏代碼
  1. package com.sishuok.proxy;  
  2.   
  3. public interface Interface {  
  4.     public void sayHello();  
  5. }  
Java代碼  收藏代碼
  1. package com.sishuok.proxy;  
  2.   
  3. public class Target implements Interface {  
  4.     public void sayHello() {  
  5.         System.out.println("===hello");  
  6.     }  
  7. }  

2.1、spring-config.xml配置:  

Java代碼  收藏代碼
  1. <bean id="myBean" class="com.sishuok.proxy.MyBean">  
  2.     <property name="target" ref="target"/>  
  3. </bean>  
  4.   
  5. <bean id="target" class="com.sishuok.proxy.Target"/>  
  6. <bean id="myAspect" class="com.sishuok.proxy.aspect.MyAspect"/>  
  7.   
  8. <aop:config proxy-target-class="true">  
  9.     <aop:aspect ref="myAspect">  
  10.         <aop:before method="before" pointcut="execution(* com.sishuok.proxy.*.*(..))"/>  
  11.     </aop:aspect>  
  12. </aop:config>  

 

aop:config proxy-target-class="true"走CGLIB類代理,而不是JDK動態代理。

 

2.2、配置文件other-config.xml  

Java代碼  收藏代碼
  1. <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>  

 

 

問題分析:

1、首先spring-config.xml配置文件的<aop:config>會交給AopNamespaceHandler處理: 

Java代碼  收藏代碼
  1. http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler  

 

Java代碼  收藏代碼
  1. registerBeanDefinitionParser("config"new ConfigBeanDefinitionParser());  

2、aop:config委託給ConfigBeanDefinitionParser處理,並通過如下代碼註冊自動代理創建器: 

Java代碼  收藏代碼
  1. configureAutoProxyCreator(parserContext, element);  
Java代碼  收藏代碼
  1. private void configureAutoProxyCreator(ParserContext parserContext, Element element) {  
  2.     AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);  
  3. }  

最終會委託給如下代碼(中間過程省略,都是委託):  

Java代碼  收藏代碼
  1. public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {  
  2.     return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);  
  3. }  

  

Java代碼  收藏代碼
  1. private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) {  
  2.     Assert.notNull(registry, "BeanDefinitionRegistry must not be null");  
  3.     if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {  
  4.         BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);  
  5.         if (!cls.getName().equals(apcDefinition.getBeanClassName())) {  
  6.             int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());  
  7.             int requiredPriority = findPriorityForClass(cls);  
  8.             if (currentPriority < requiredPriority) {  
  9.                 apcDefinition.setBeanClassName(cls.getName());  
  10.             }  
  11.         }  
  12.         return null;  
  13.     }  
  14.     RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);  
  15.     beanDefinition.setSource(source);  
  16.     beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);  
  17.     beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);  
  18.     registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);  
  19.     return beanDefinition;  
  20. }  

即最終會創建一個AspectJAwareAdvisorAutoProxyCreator,如上代碼意思就是:如果當前容器中已經有一個AUTO_PROXY_CREATOR_BEAN_NAME,那麼根據實際情況修改配置,否則添加一個(也就是說一個容器中不管有多少個aop:config也最多隻添加一個AspectJAwareAdvisorAutoProxyCreator

 

2、接着會添加other-config.xml的DefaultAdvisorAutoProxyCreator,即又添加了一個自動代理創建器;

 

注意 :這兩個AutoProxyCreator都是BeanPostProcessor,具體參考如下兩篇文章,此處就不詳述了:

 

所以問題就出現了(以下順序默認應該看成無序,可以修改order屬性來指定順序,但沒有作用):

  1. AspectJAwareAdvisorAutoProxyCreator會創建一個代理(因爲<aop:config proxy-target-class="true">),這個代理是CGLIB代理;
  2. DefaultAdvisorAutoProxyCreator會對代理對象再創建代理,但是因爲沒有告訴它代理類,所以默認代理接口,即代理是JDK動態代理;

 

即雖然AspectJAwareAdvisorAutoProxyCreator創建了類代理,但DefaultAdvisorAutoProxyCreator還是創建了JDK動態代理(接口)。

 

 

解決辦法:

1、DefaultAdvisorAutoProxyCreator也是cglib代理: 

Java代碼  收藏代碼
  1. <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">  
  2.     <property name="proxyTargetClass" value="true"/>  
  3. </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

 

  1. 見到$Proxy開頭的類,基本上可以確定是JDK動態代理
  2. 此處可以看到$Proxy0 實現了 一堆接口,能看到一個org.springframework.cglib.proxy.Factory,從這個已經能判斷出其是二次代理了,即當前的JDK動態代理代理了CGLIB代理。
  3. 如果見到如輸出的class是com.sishuok.proxy.Target$$EnhancerByCGLIB$$12c22b67,那就是CGLIB代理了。

 

 

總結

  1. 首選如<aop:config>,而不是自己定義如×××AutoProxyCreator,而且使用<aop:config>方式能更好的描述切面。
  2. 觀察類是$Proxy…… 還是 ……$$EnhancerByCGLIB$$……,來判斷是JDK動態代理還是CGLIB代理。
  3. 通過觀察$Proxy的實現中是否包含org.springframework.cglib.proxy.Factory來判斷是否是二次代理。
  4. 通過《Spring事務不起作用 問題彙總》 中介紹的方式查看是否創建了代理。 

分析完畢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章