`
jinnianshilongnian
  • 浏览: 21459153 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2409976
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:3001539
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5634523
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:258593
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1594666
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:249453
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5851387
Group-logo
跟我学Nginx+Lua开...
浏览量:699619
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:782158
社区版块
存档分类
最新评论

spring的二次代理原因及如何排查

阅读更多

最近一个朋友使用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属性来指定顺序,但没有作用):

  1. AspectJAwareAdvisorAutoProxyCreator会创建一个代理(因为<aop:config proxy-target-class="true">),这个代理是CGLIB代理;
  2. 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

 

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

分析完毕。

 

6
1
分享到:
评论
7 楼 sgq0085 2016-06-15  
非常感谢 在使用Shiro的时候同样错误的使用了DefaultAdvisorAutoProxyCreator导致相同的问题
6 楼 zqb666kkk 2015-10-12  
非常 感谢  解决了我的问题 可以下班了!
5 楼 focus2008 2015-07-06  
从源代码来看,感觉是不可能同时创建两个APC(AutoProxyCreator)的:
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary
AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary
这三个方法实现的都是AopConfigUtils类的同一个方法:
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source)
该方法的源代码,博主文章中已经贴出来了,看其代码,对应于APC的id值,只有一个值:
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
"org.springframework.aop.config.internalAutoProxyCreator";

一个id值,是不可能对应于多个bean的。所以我感觉而已是不可能同时创建两个APC的,可能是一个配置将 APC 改成 CGLIB 的,另一个配置又试图将其改成 JDK动态代理的:
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
}
最后无所适从,报错。
4 楼 jinnianshilongnian 2013-07-16  
kjmmlzq19851226 写道
如果反过来说,CGLIB代理代理了JDK动态代理是不是就不会有问题了?

是的,
3 楼 kjmmlzq19851226 2013-07-16  
如果反过来说,CGLIB代理代理了JDK动态代理是不是就不会有问题了?
2 楼 jinnianshilongnian 2013-07-09  
amoszhou 写道
那天这个问题,咱们纠结了那么久。哈哈。

最初发现问题的所在的时候,是用2分发去掉配置文件排 查才发现的

对啊,找这种问题很耗时间,总感觉是对的,越是对的地方可能最容易出错。细节啊细节
1 楼 amoszhou 2013-07-09  
那天这个问题,咱们纠结了那么久。哈哈。

最初发现问题的所在的时候,是用2分发去掉配置文件排 查才发现的

相关推荐

    spring cloud demo

    Spring Cloud是一系列框架的集合,它为开发者在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)操作中提供了简单易用的开发工具。...

    spring3.1.2源码包

    3. 提升问题排查能力,理解异常背后的深层次原因。 总结,Spring 3.1.2源码的学习是一次深入理解Java企业级开发的宝贵旅程,通过细致的研究,开发者可以更高效地利用Spring框架,提升软件开发的质量和效率。同时,...

    SpringCloud 20道面试题和答案.docx

    如Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Consul等,提供了搭建分布式系统及微服务常用的工具,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式...

    《深入理解Spring Cloud与微服务构建》word版本

    1. **Spring Cloud简介**:Spring Cloud是基于Spring Boot的云应用开发工具集,它为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式...

    springcloud-nacos-demo.zip

    SpringCloud是基于Spring Boot实现的云应用开发工具集,它为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)中快速...

    Spring源代码解析(一):IOC容器.doc

    在XmlBeanFactory之上,Spring提供了更抽象的实现,如AbstractBeanFactory和DefaultListableBeanFactory,它们通过模板模式提供了更多的功能,如依赖注入、AOP代理等。 在实际使用中,Spring通常使用...

    struts+hibernate+spring源码

    4. **扩展与定制**:对于有特殊需求的项目,可以基于源码进行二次开发或定制。 总的来说,这份"Struts+Hibernate+Spring源码"集合是一份宝贵的资源,它提供了深入学习和研究这些框架的机会,对提升Java开发者的技术...

    SpringCloud演讲搞.rar

    SpringCloud是一个集合了众多开源项目的整体解决方案,它为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等)中快速...

    springcloud-learning-master.zip

    Spring Boot 提供了一种快速构建独立的、生产级别的基于Spring的应用程序的方式,而Spring Cloud则为开发分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、...

    springcloud开发测试工具集

    Spring Cloud是基于Spring Boot实现的一系列微服务解决方案,它提供了服务注册与发现、配置中心、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等多种功能,让开发者...

    基于spring cloud项目源码源码.rar

    Spring Cloud是基于Spring Boot实现的一套微服务解决方案,它为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)操作...

    Spring Cloud Greenwich SR2 官方文档

    Spring Cloud是基于Spring Boot构建的,旨在简化开发分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)的服务。以下是对Spring Cloud ...

    人工智能-项目实践-检索系统-基于SpringCloud的新闻检索与推荐系统设计与实现

    Spring Cloud是基于Spring Boot的一个框架集合,为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等)中快速构建一些...

    spring技术内幕书籍

    源码分析是提升技术水平的有效途径,它可以帮助开发者更好地理解和优化框架的使用,甚至进行二次开发和定制。 阅读《Spring技术内幕》,不仅能够提高对Spring的理解,还能学习到设计模式的实践应用,如工厂模式、...

    spring cloud中文文档

    Spring Cloud 是一个基于Java的微服务开发框架,它简化了构建分布式系统中的诸多挑战,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等。...

    springboot2.0+springcloud+Eureka+fegin分布式项目demo

    SpringCloud则是基于Spring Boot实现的云应用开发工具集,它提供了配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等多种功能,帮助开发者快速构建...

    SpringCloud资源文件.zip

    Spring Cloud作为微服务架构的重要组成部分,为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等)中快速构建一些常见...

    springcloud服务提供者

    Spring Cloud是Spring官方提供的一个快速构建分布式系统的工具集,它为开发者提供了在分布式配置、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等多种功能,...

    spring源码解析

    3. **AOP实现**:Spring的AOP基于代理模式,有两种代理方式:JDK动态代理和CGLIB代理。Advice(通知)和Pointcut(切点)是AOP的关键概念,它们在AbstractAdvisorAutoProxyCreator类中被处理。 4. **Spring MVC**:...

Global site tag (gtag.js) - Google Analytics