上周自己写代码的时候发现了一件怪事,好好研究分析了一下JDK动态代理和CGLib2AopProxy一些基本原理,这篇文章写了蛮久的,特与大家分享。
先上代码,片段1是LoanRateAdjustTransaction的单元测试代码,片段2是LoanRateAdjustTransaction中的实现:
public void test1() {
// do something
LoanRateAdjustRateParam param = new LoanRateAdjustParam();
loanRateAdjustTransaction.validateParam(param);
Result result = loanRateAdjustTransaction.execute(param);
// do some asserts
}
public final void validateParam(LoanRateAdjustRateParam param) {
//do something
try{
loanAcctManager.doSomething1();
} catch(Exception e) {
// do nothing
}
}
@Transactional
public void execute(LoanRateAdjustRateParam param){
// do something
try{
loanAcctManager.doSomething2();
} catch(Exception e) {
// do nothing
}
...
}
问题:发现在执行到loanAcctManager.doSomething1();会NullPointerException,即loanAcctManager是null,loanAcctManager.doSomething2()时却不会报NullPOinterException。
“同一个线程,同一个bean”中两行不同代码所引用的对象(loanAcctManager)竟然一个是null(code2 line4),一个却不是null(code2 line13)!!!
难道片段1的line4和line5两行代码所使用的loanRateAdjustTransaction不是同一个对象不成?不可能吧,Spring没这么脑残的把?!下面做一个简单的试验。
实验一:code1中加上两行日志输出代码,
public void test1() {
// do something
LoanRateAdjustRateParam param = new LoanRateAdjustParam();
logger.info(loanRateAdjustTransaction.hashCode());
loanRateAdjustTransaction.validateParam(param);
logger.info(loanRateAdjustTransaction.hashCode());
Result result = loanRateAdjustTransaction.execute(param);
// do some asserts
}
运行结果日志:
引用
INFO impl.LoanRateAdjustTransactionTest - 405739368
INFO impl.LoanRateAdjustTransactionTest - 405739368
两行日志输出hashCode是一样的,说明是同一个对象!
那还能有什么别的原因? validateParam和execute方法存在一个细微的差别:execute方法上头有个@Transactional,而validateParam方法是final的。
难道原因前者?后者?或是两者?
实验二:在code2两个方法结束的地方加入日志代码
loanAcctManager.doSomething1();
logger.error(this.hashCode());
...
loanAcctManager.doSomething2();
logger.error(this.hashCode());
logger.error(this.hashCode());
运行日志:
引用
INFO impl.LoanRateAdjustTransactionTest - 405739541
ERROR impl.LoanRateAdjustTransaction - 405739541
INFO impl.LoanRateAdjustTransactionTest - 405739541
ERROR impl.LoanRateAdjustTransaction - 33408816
实验三:将execute方法上的@Transactional标签去掉,保留validateParam方法的final约束
单元测试不再抛NullPointerException,测试代码运行正常,问题不再出现!运行日志
引用
INFO impl.LoanRateAdjustTransactionTest - 12563781
ERROR impl.LoanRateAdjustTransaction - 12563781
INFO impl.LoanRateAdjustTransactionTest - 12563781
ERROR impl.LoanRateAdjustTransaction - 12563781
实验四:将validateParam方法的final约束去掉,保留execute方法上的@Transactional标签
单元测试不再抛NullPointerException,测试代码运行正常,问题不再出现!运行日志同实验三
看来,原因是@Transactional和final联合发生的作用,那具体原因是什么呢?下面需要对Spring AOP机制进行一些解释:
Spring AOP是基于CGLib来对Spring容器中bean的动态行为进行封装,在加载class文件时通过动态代理的方式修改、丰富原方法的行为,比如标签式事务(@Transactional)就是在方法调用前以AOP的方式启动了事务。
一. 静态地看(内存结构和类图),loanRateAdjustTransaction在Spring容器中的实际上是以类似下图的方式存在的:
图一说明: loanRateAdjustTransaction已经被封装成两外一个对象了,code1的测试代码所使用的loanRateAdjustTransaction其实是代理类$ProxyN的对象,该对象是会持有loanOpenTransaction中的所有方法,并以Method xxx保存(有兴趣的同学可以再研究一下JDK动态代理的原理)。
图二说明: Spring通过CGLib2AopProxy,对定义的loanRateAdjustTransaction(object)进行代理,通过创建一个继承实现类的子类(JDK Proxy采取的方式是复用而非继承),在运行时动态修改子类的代码来实现的。CGLib2AopProxy在实例化后,其实是会根据一定的规则(如*)将从LoanRateAdjustTransaction(class)继承下来的Method作为自身的成员进行保存,并在这些Method被调用时进行拦截,而被final字段标示的Method是一个例外,因为final方法是不能被继承的,即:被final字段标识过的方法不会被代理! 如果定义一个class是final,而又有方法使用了类似于@Transactional的Spring标签时,那么如果在Spring的bean配置文件中加入该类型的bean实例,Spring容器初始化时是会报错的,因为CGlibAopProxy尝试继承这个class。
二, 动态地看(调用过程)
1. 所有被代理过的Method在被外部方法调用时,调用请求都首先会被代理CGlib2AopProxy拦截并执行框架所需的动作、添加其行为,
如loanRateAdjustTransaction.execute(param)之前会先通过Spring的TransactionManager启动事务,然后再调用CGlib2AopProxy实例中的loanRateAdjustTransaction引用的execute方法,即
// proxy.transactionManager.beginTransaction();
proxy.loanRateAdjustTransaction.execute(param);
…
// proxy.transactionManager.commitTransaction();
2. 而调用loanRateAdjustTransaction.validateParam(param)时,实质是proxy.super.validateParam(param),与proxy.loanRateAdjustTransaction.validateParam(param)有很大的差别,
差别在于proxy.loanRateAdjustTransaction是Spring容器中的对象,在容器初始化时经过了bean之间ref的适配,而proxy则没有,因此首先会看到实验二中的日志,testCase中的LoanRateAdjustTransaction实例和validateParam中的this都是proxy,而execute方法中的this是proxy. loanRateAdjustTransaction。
实验五:我们单独将validateParam(param)方法的final约束去掉
引用
INFO impl.LoanRateAdjustTransactionTest - 28171907
ERROR impl.LoanRateAdjustTransaction - 27713748
INFO impl.LoanRateAdjustTransactionTest - 28171907
ERROR impl.LoanRateAdjustTransaction – 27713748
日志表明,LoanRateAdjustTransactionTest中的loanRateAdjustTransaction相同(proxy),但不同于实际运行时和execute、validateParam方法中的this(spring容器中的loanRateAdjustTransaction)
如果大家自己理解了上面的五个实验,相信大家应该已经彻底理解了CGLib代理是如何一个情况了。
其他相关文章推荐:
http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/
http://www.ibm.com/developerworks/cn/java/j-lo-proxy2/?ca=drs-cn-0127
http://www.ibm.com/developerworks/cn/java/j-jtp08305.html
- 大小: 3.6 KB
- 大小: 2.3 KB
分享到:
相关推荐
spring之AOP(动态代理),包括jdk动态代理和CGLib动态代理
AOP之JDK动态代理和CGLib动态代理 ,具体效果和过程看博文 http://blog.csdn.net/evankaka/article/details/45195383
主要对Spring AOP的相关概念和简单的静态代理、动态代理以及常见的几种AOP配置方式做总结学习。主要包括:1. AOP的常见概念 2. 静态代理 3. jdk动态代理 4. Aspectj and Aspectjweaver 5. **aop-config** 6. CGLIB ...
Spring框架的AOP中重要的一个知识点,动态代理,springAOP框架会根绝实际情况选择使用jdk的动态代理还是cglib的动态代理
SpringAOP动态代理 Spring AOP 使用的动态代理主要有两种方式:JDK 动态代理和 CGLIB 代理。 JDK 动态代理:用于代理实现了接口的类。Spring 会使用 java.lang.reflect.Proxy 类来创建代理对象。 CGLIB 代理:用于...
通过学习它们的原理和实际应用,您将能够更好地理解和利用Spring AOP来提高您的应用程序的可维护性和可扩展性。 内容亮点: JDK动态代理: 我们将详细介绍JDK动态代理的概念和工作原理。您将了解如何使用Java的...
cglib实现动态代理所依赖的jar包 在学习spring中会用到
如果想使用CGLIB的技术来生成代理对象,那么需要引入CGLIB的开发的jar包,在Spring框架核心包中已经引入了CGLIB的开发包了。所以直接引入Spring核心开发包即可!
Spring-AOP-利用java中的动态代理和Spring的拦截器做到AOP
cglib代理, 1.cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。 2.cglib封装了asm,可以在运行期动态生成新的class。 3.cglib用于AOP,...
Spring AOP实现方法之一:CGLIB 实现AOP功能
spring aop 编程,cglib ,切面编程
Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现.doc
如果一个类实现了一个或多个接口,那么Spring就会使用默认的JDK动态代理,如果没有实现任何接口,就会使用cglib来代理。当然我们也可以手动改变这些设置。这也是比较容易掉坑的部分,如果设置错了代理方式,那么在...
package com.gc.cglib下为:aop方式cglib代理 package com.gc.dynproxy下为:aop方式动态代理 package com.gc.javaproxy下为:java代理机制实现 package com.gc.proxy下为:自定义代理模式(面向接口编程) ...
Spring 作为 Java 中最流行的框架,主要归功于其提供的 IOC 和 AOP 功能。本文将讨论 Spring AOP 的实现。第一节将介绍 AOP 的相关概念,若熟悉可跳过,第二节中结合源码介绍 Spring 是如何实现 AOP 的各概念。 1. ...
SpringAOP之探秘(动态代理、责任链模式、注解使用)- 详情:https://blog.csdn.net/Dream_Weave/article/details/85008674
基于Cglib简单实现Spring体系(Ioc+Aop+Mvc)基于Cglib简单实现Spring体系(Ioc+Aop+Mvc)基于Cglib简单实现Spring体系(Ioc+Aop+Mvc)基于Cglib简单实现Spring体系(Ioc+Aop+Mvc)基于Cglib简单实现Spring体系(Ioc+Aop+Mvc)...
cglib动态代理相关jar包,亲测可用cglib动态代理相关jar包,亲测可用cglib动态代理相关jar包,亲测可用
哪怕没有看过源码的同学也应该知道,AOP是通过动态代理实现的,动态代理又分为两个部分:JDK动态代理和CGLIB动态代理 确实,Spring也就是通过这两种方式来实现AOP相关功能,下面就通过源码来简单求证下