`
zzproc
  • 浏览: 16711 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Spring AOP和CGLib动态代理的原理

阅读更多
  上周自己写代码的时候发现了一件怪事,好好研究分析了一下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框架中JDK动态代理和cglib动态代理

    Spring 框架中 JDK 动态代理和 CGLIB 动态代理是 Spring AOP 中一个非常重要的知识点。Spring AOP 框架会根据实际情况选择使用 JDK 的动态代理还是 CGLIB 的动态代理。 JDK 动态代理是 Java 自带的动态代理机制,它...

    spring之AOP(动态代理)

    在Spring中,AOP主要通过两种动态代理技术实现:JDK动态代理和CGLIB动态代理。 首先,让我们详细了解一下JDK动态代理。JDK动态代理基于Java的接口实现,它适用于目标对象实现了至少一个接口的情况。在运行时,JDK...

    AOP之JDK动态代理和CGLib动态代理

    Spring框架是AOP实现的一个典范,它提供了两种主要的动态代理方式:JDK动态代理和CGLib动态代理。 **JDK动态代理**: JDK动态代理基于Java的反射API实现,适用于接口代理。当目标对象实现了至少一个接口时,Spring...

    反射实现 AOP 动态代理模式(Spring AOP 的实现原理)

    Spring AOP支持不同的代理策略,包括JDK动态代理和CGLIB代理。如果被代理的类没有实现接口,Spring AOP会采用CGLIB来生成代理对象。CGLIB(Code Generation Library)是一个开源的代码生成库,它允许运行时在内存中...

    spring_aop_cglib的实现方式

    在Spring配置中,如果我们希望使用CGLIB代理,可以在`<aop:config>`或`<aop:aspectj-autoproxy>`元素下添加`<aop:proxy>`子元素,并设置`proxy-target-class="true"`。例如: ```xml <aop:config> <aop:aspect id=...

    Jdk动态代理和cglib动态代理原理

    在实际开发中,如Spring AOP框架就同时支持JDK和CGLIB动态代理,根据目标类是否实现接口自动选择合适的代理方式。 总结来说,JDK动态代理和CGLIB动态代理都是为了在运行时提供对目标对象的增强,它们通过不同的实现...

    Spring-AOP-JDK动态代理

    5. **配置代理**:Spring会根据目标对象是否实现了接口来决定使用JDK动态代理还是CGLIB代理。如果目标对象实现了接口,Spring会选择JDK动态代理。动态代理类会继承自`java.lang.reflect.Proxy`,并实现目标对象的...

    cglib及其依赖包

    CGLib,全称为Code Generation Library,是一个强大的高性能的代码生成库,它在Java世界中被广泛应用,尤其是在动态代理和AOP(面向切面编程)领域。这个库的主要功能是能够在运行时动态创建类或者增强已有类的功能...

    死磕Spring之AOP篇 - Spring AOP两种代理对象的拦截处理(csdn)————程序.pdf

    在 Spring 中,AOP 的实现主要依赖于代理模式,有两种代理方式:JDK 动态代理和 CGLIB 动态代理。 JDK 动态代理是基于接口的,它要求被代理的目标对象必须实现至少一个接口。Spring 使用 `java.lang.reflect.Proxy`...

    JDK动态代理 spring aop 的原理

    现在让我们深入探讨JDK动态代理和Spring AOP的原理。 首先,JDK动态代理基于Java的反射机制,通过`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口来实现。Proxy类用于创建一个代理对象,...

    Spring AOP 中 JDK 和 CGLib 动态代理哪个更快?.docx

    Spring AOP 中 JDK 和 CGLib 动态代理哪个更快?

    Java动态代理(Spring Aop原理)

    Spring AOP提供了两种代理方式:JDK动态代理和CGLIB代理。JDK动态代理用于目标对象实现接口的情况,而CGLIB代理则用于目标对象没有实现接口的情况。Spring默认使用JDK动态代理,但如果目标对象没有实现接口,它会...

    springAOP配置动态代理实现

    CGLIB代理提供了更广泛的应用场景,但相比JDK动态代理,它的性能稍差。 3. **代理选择**:Spring默认优先使用JDK动态代理,如果无法满足条件(即目标类没有实现接口),则会自动切换到CGLIB。可以通过配置`proxy...

    SpringAOP的实现机制(底层原理)、应用场景等详解,模拟过程的实例

    通过学习它们的原理和实际应用,您将能够更好地理解和利用Spring AOP来提高您的应用程序的可维护性和可扩展性。 内容亮点: JDK动态代理: 我们将详细介绍JDK动态代理的概念和工作原理。您将了解如何使用Java的...

    springAop.rar_AOP java_cglib_spring aop

    在提供的压缩包文件中,"www.pudn.com.txt"可能是下载来源的说明或者包含一些额外的信息,而"springAop"可能是一个包含Spring AOP示例代码的文件。对于学习和理解Spring AOP以及CGLIB的用法,分析这个文件的内容将...

    基于MAVEN项目的CGLib动态代理原理及实现

    它广泛用于许多AOP(面向切面编程)框架,如Spring AOP和dynaop,作为JDK动态代理的替代品,当目标类不支持接口时,CGLib能发挥重要作用。 3. CGLib工作原理 CGLib通过继承的方式创建代理对象。它会生成一个目标类的...

    Spring AOP面向方面编程原理:AOP概念

    Spring提供了两种类型的代理:JDK动态代理和CGLIB代理。 8. **编织(Weaving)**:是指将通知插入到目标对象的过程。Spring AOP是在运行时完成的织入过程,即在程序运行时动态地添加通知。 #### 三、Spring AOP的...

    浅谈JDK动态代理与CGLIB代理去区别

    在Java开发中,动态代理和CGLIB代理是两种常见的面向切面编程(AOP)实现方式,它们都用于在不修改原有代码的情况下,增强或扩展对象的功能。本篇文章将深入探讨JDK动态代理和CGLIB代理的区别,以及它们在实际应用中...

    反射实现 AOP 动态代理模式(Spring AOP 的实现 原理) - Java 例子 -

    在Java编程中,AOP(面向切面编程)是一种强大的设计模式,它允许开发者将关注点分离,将横切关注点(如日志、事务管理)与核心业务逻辑解耦...理解Spring AOP的工作原理和反射机制对于开发高质量的Java应用至关重要。

    jdk与cglib动态代理与底层实现

    JDK和CGLIB是Java中实现动态代理的两种主要方式,它们在Spring框架中扮演着关键角色,尤其是在AOP(面向切面编程)中。 1. **JDK动态代理**: JDK动态代理基于Java的接口机制实现,因此,要使用JDK动态代理,被...

Global site tag (gtag.js) - Google Analytics