从Spring AOP的原理理解@Transactional失效问题

在正确配置了Spring事务管理后,或许在某些场景下,你可以写出如下代码:

class T {

    public int createFirst(){
       //dosometing....
       try {
           this.createSecond();
       }catch (Exception e){
          throw e;
       }
       //dosometing....
       return 0;
    }


    @Transactional
    public int createSecond(){
       //dosometing with db....

    }
}

然后在外部通过Spring容器获取T的实例t,当你调用t.createFirst()方法的时候,你会发现添加了@Transactional注解的createSecond()方法并没有运行在事务中。

为了了解这个问题的原理,首先先需要了解Spring 是如何处理注解的(@Transaction 或 @Async)。

AOP

面向切面编程,通俗一点将即是在要执行的方法前或后执行一段代码。类使用Spring MVC中的过滤器,在请求前后执行额外的逻辑,并且该逻辑对实际的方法是透明的。

AOP的实现原理

  • 编译时织入:在代码编译时,把切面代码融合进来,生成完整功能的Java字节码,这就需要特殊的Java编译器了,AspectJ属于这一类

  • 类加载时织入:在Java字节码加载时,把切面的字节码融合进来,这就需要特殊的类加载器,AspectJ和AspectWerkz实现了类加载时织入

  • 运行时织入:在运行时,通过动态代理的方式,调用切面代码增强业务功能,Spring采用的正是这种方式。动态代理会有性能上的开销,但是好处就是不需要神马特殊的编译器和类加载器啦,按照写普通Java程序的方式来就行了!

Spring AOP 属于运行时织入,即使用代理模式。或许你看到Spring AOP依赖了AspectJ包会以为Spring AOP是AspectJ来实现的。但其他并不是,Spring AOP只是使用了和AspectJ一样的注解但并没有使用 AspectJ 的编译器或者织入器(Weaver),底层AOP实现与AspectJ是不同的。Spring AOP 无需使用任何特殊命令对 Java 源代码进行编译,它采用运行时动态地、在内存中临时生成“代理类”的方式来生成 AOP 代理。

Spring AOP的代理实现

  • JDK动态代理:JDK动态代理技术。通过需要代理的目标类的getClass().getInterfaces()方法获取到接口信息(这里实际上是使用了Java反射技术。getClass()和getInterfaces()函数都在Class类中,Class对象描述的是一个正在运行期间的Java对象的类和接口信息),通过读取这些代理接口信息生成一个实现了代理接口的动态代理Class(动态生成代理类的字节码),然后通过反射机制获得动态代理类的构造函数,并利用该构造函数生成该Class的实例对象(InvokeHandler作为构造函数的入参传递进去),在调用具体方法前调用InvokeHandler来处理。

  • CGLib动态代理:字节码技术。利用asm开源包,把代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。采用非常底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。

在我们实际使用Spring AOP时,它包括基于XML配置的AOP和基于@AspcetJ注解的AOP,这两种方法虽然在配置切面时的表现方式不同,但底层都是使用动态代理技术(JDK代理或CGLib代理)。

现在回头来看本文开头的代码:

createFirst()方法里面直接调用createSecond(……)方法,这里还隐含一个关键字,那就是this,实际上这里调用是这样的:this.createSecond(),this是当前对象。当前对象是T,问题就出在这里,因为要想用事务执行createSecond(……),必须用代理对象执行,因为代理对象会拦截到@Transactional来执行相关的增强,但是此时却直接用T对象调用,绕过了代理对象增强的部分,也就是说代理增强部分失效,@Transactional注解失效。

如果在外部通过Spring容器获取T实例,然后直接调用createSecond方法,此时createSecond()是被代理的,在代理对象中判断到该方法有@Transactional,则会执行该注解需要的前置增强后(开启事务),然后通过invoke,用实际T对象来调用addOrder()方法执行业务逻辑,然后执行后置增强(回滚or提交)。

特殊场景的解决办法:

没有用代理对象执行createSecond(……),被T对象抢占了先机。那么解决就是要让代理对象来执行createSecond(……)。

T t = (T) AopContext.currentProxy(); 
//获取代理对象
t.createSecond(); 
//通过代理对象调用createSecond

//需要在@EnableAspectJAutoProxy添加属性值。
//@EnableAspectJAutoProxy(exposeProxy = true)
    原文作者:AOP
    原文地址: https://blog.csdn.net/canot/article/details/80855439
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞