最近一个朋友使用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事务不起作用 问题汇总》 中介绍的方式查看是否创建了代理。
分析完毕。
相关推荐
Spring Cloud是基于Spring Boot的一系列工具集,旨在快速建立分布式系统中的一些常见模式(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话和集群状态)。...
Spring Cloud是一系列框架的集合,它为开发者在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)操作中提供了简单易用的开发工具。...
3. 提升问题排查能力,理解异常背后的深层次原因。 总结,Spring 3.1.2源码的学习是一次深入理解Java企业级开发的宝贵旅程,通过细致的研究,开发者可以更高效地利用Spring框架,提升软件开发的质量和效率。同时,...
SpringCloud是一套基于Spring Boot实现的微服务云应用开发工具集,它提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)操作的...
Spring Cloud是基于Spring Boot实现的一套微服务解决方案,它为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)操作...
如Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Consul等,提供了搭建分布式系统及微服务常用的工具,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式...
1. **Spring Cloud简介**:Spring Cloud是基于Spring Boot的云应用开发工具集,它为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式...
- **发展历程**:Spring Security的发展历程可以追溯到早期的ACEGI Security项目,后来被集成到Spring框架中,并经过多次迭代更新,形成了如今功能强大且灵活的Spring Security框架。 - **获取方式**: - **项目...
SpringCloud是基于Spring Boot实现的云应用开发工具集,它为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)中快速...
在XmlBeanFactory之上,Spring提供了更抽象的实现,如AbstractBeanFactory和DefaultListableBeanFactory,它们通过模板模式提供了更多的功能,如依赖注入、AOP代理等。 在实际使用中,Spring通常使用...
4. **扩展与定制**:对于有特殊需求的项目,可以基于源码进行二次开发或定制。 总的来说,这份"Struts+Hibernate+Spring源码"集合是一份宝贵的资源,它提供了深入学习和研究这些框架的机会,对提升Java开发者的技术...
SpringCloud是一个集合了众多开源项目的整体解决方案,它为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等)中快速...
Spring Cloud是基于Spring Boot实现的一系列微服务解决方案,它提供了服务注册与发现、配置中心、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等多种功能,让开发者...
Spring Boot 提供了一种快速构建独立的、生产级别的基于Spring的应用程序的方式,而Spring Cloud则为开发分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、...
2. **生成代理对象**:当Spring容器加载并创建Bean时,它会检查是否有使用了`@Transactional`注解的方法或类,并为这些方法或类生成代理对象。 3. **注入事务配置**:在代理对象中注入事务相关的配置,如事务的传播...
它利用Spring Boot的自动化配置特性简化了分布式系统基础设施的开发,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等。 - **配置管理**: 提供...
Spring Cloud是基于Spring Boot构建的,旨在简化开发分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)的服务。以下是对Spring Cloud ...
### Spring及AOP应用(事务与集成):深入解析 #### 一、Spring框架概述 Spring框架是由Rod Johnson创建的一个轻量级的开源框架,它最初来源于他的书籍《Expert One-on-One J2EE Design and Development》。Spring的...