1. 程式人生 > >spring二次代理的問題

spring二次代理的問題

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

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

程式碼如下:

1、介面及目標類 

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

2.1、spring-config.xml配置:  

Java程式碼  收藏程式碼
  1. <bean id="myBean" class="com.sishuok.proxy.MyBean">  
  2.     <property name="target" ref="target"/>  
  3. </bean>  
  4. <bean id="target" class="com.sishuok.proxy.Target"
    />  
  5. <bean id="myAspect" class="com.sishuok.proxy.aspect.MyAspect"/>  
  6. <aop:config proxy-target-class="true">  
  7.     <aop:aspect ref="myAspect">  
  8.         <aop:before method="before" pointcut="execution(* com.sishuok.proxy.*.*(..))"/>  
  9.     </aop:aspect>  
  10. </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來判斷是否是二次代理。

分析完畢。