从根上理解为什么你的 @Transational 注解失效了

/ Java / 没有评论 / 1466浏览

从根上理解为什么你的 @Transational 注解失效了

在面试中,很多人可能都会被问到“@Transational 注解失效了,可能原因是什么?”这类问题。

网上有很多答案。比如,@Transational 只能作用在 public 方法上。再比如,在同一个类中,一个 nan-transactional 的方法去调用 transactional 的方法,事务也会失效等。其实这个问题,不仅困惑着你们,stackoverflow 上也非常的热门。我总结一下,你可以根据下面的方式进行排查你的代码。

  1. @Transactional 加于private方法,无效
  2. @Transactional 加于未加入接口的public方法, 再通过普通接口方法调用,无效
  3. @Transactional 加于接口方法,无论下面调用的是private或public方法,都有效
  4. @Transactional 加于接口方法后,被本类普通接口方法直接调用,无效
  5. @Transactional 加于接口方法后,被本类普通接口方法通过接口调用,有效
  6. @Transactional 加于接口方法后,被它类的接口方法调用,有效
  7. @Transactional 加于接口方法后,被它类的私有方法调用后,有效

Transactional 是否生效,仅取决于是否加载于接口方法,并且是否通过接口方法调用(而不是本类调用)。

为什么会这样呢?我今天简单的从代理的角度给大家讲一讲。

@Transactional 失效的背后其实就是 Spring 代理机制造成的。说白了,就是通过 Spring 容器获取的类对象,很多情况下并不是原类,而是被 Spring 修饰过了的代理类。然后,你在被代理的方法中调用当前类的其它方法,此时就是调用的原生类中的方法。

听不懂没关系,我下面用伪代码给你演示一下,你就知道了。

假如现在要执行 Xttblog 类对象的方法 invoke(),而用 Spring 代理后,会对 Xttblog 类做修饰,也就是会对 Xttblog 的 test 方法生成一个代理类。

执行,调用代理方法 proxyXttblogbean.invoke():

before 操作
invoke(bean,Xttblog)
after 操作

实际你运行的是 Spring 修饰过的代理类 proxyXttblogbean.invoke()方法。

这样就会造成一个问题,如果你在 invoke() 中调用 Xttblog 类的其余方法 invoke2(),此时 invoke2() 是直接调用的原类的 Xttblog.invoke2(),而不是代理类 proxyXttblogbean.invoke2()。这时 Xttblog.invoke2() 上的一些注解全部都没有被增强修饰。所以,最终的结果就和你的预期不一致了。

从根上知道了发生这类问题的原因,问题就好解决了。比如,我们可以想办法手动拿到代理对象。类似下面的伪代码:

// 在invoke()方法内部调用invoke2()方法
// 1.直接调用invoke2(),注解失效。
invoke2()
// 2.拿到代理类对象,再调用invoke2()。
((Xttblog)AopContext.currentProxy()).invoke2()

至于 private 方法上的注解也失效,原因也很简单。因为 Spring 不管使用的是 JDK 动态代理还是 CGLIB 动态代理,一个是针对实现接口的类,一个是通过子类实现。无论是接口还是父类,显然都不能出现 private 方法,否则子类或实现类都不能覆盖到。

如果方法为 private,那么在代理过程中,根本找不到这个方法,引起代理对象创建出现问题,也导致了有的对象没有注入进去。

最终示例

@Compent
public class A{
    
    @Resource
    private A a;
    
    public void call(){
        // 不会开启事务
        this.tran();
        // 开启事务
        a.tran();
        ((A)AopContext.currentProxy()).tran();
    }
    
    @Transcation
    public void tran(){
    }
}