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事务不起作用 问题汇总》 中介绍的方式查看是否创建了代理。 

分析完毕。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值