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

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分发去掉配置文件排 查才发现的

相关推荐

    Java常见面试题208道.docx

    面试题包括以下十九部分:Java 基础、容器、多线程、反射、对象拷贝、Java Web 模块、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、Mybatis、RabbitMQ、Kafka、Zookeeper、MySql...

    java开源包4

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包1

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包11

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包2

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包3

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包6

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包5

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包10

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包8

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包7

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包9

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    java开源包101

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    Java资源包01

    Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java...

    asp.net知识库

    使用.ashx文件处理IHttpHandler实现发送文本及二进制数据的方法 制作一个简单的多页Tab功能 一完美的关于请求的目录不存在而需要url重写的解决方案! 在C#中实现MSN消息框的功能 XmlHttp实现无刷新三联动ListBox 鼠标...

    JAVA上百实例源码以及开源项目

     用JAVA开发的一个小型的目录监视系统,系统会每5秒自动扫描一次需要监视的目录,可以用来监视目录中文件大小及文件增减数目的变化。 Java日期选择控件完整源代码 14个目标文件 内容索引:JAVA源码,系统相关,日历,...

    JAVA上百实例源码以及开源项目源代码

    Java目录监视器源程序 9个目标文件 内容索引:JAVA源码,综合应用,目录监视 用JAVA开发的一个小型的目录监视系统,系统会每5秒自动扫描一次需要监视的目录,可以用来监视目录中文件大小及文件增减数目的变化。...

    JAVA核心知识点整理(有效)

    1. 目录 1. 2. 目录 .........................................................................................................................................................1 JVM ........................

Global site tag (gtag.js) - Google Analytics