首页 文章详情

@Transactional 类内部调用不能前后增强的原因是什么?

业余草 | 66 2022-09-17 16:23 0 0 0
UniSMS (合一短信)

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

推荐:https://www.xttblog.com/?p=5357
自律才能自由

今年面试已经很难了,但是还有网友还不上心。今天群里有一位网友,获得了一个面试机会。但是他没抓住,非常可惜。

面试官问了一个送分题:“@Transactional 类内部调用不能前后增强的原因是什么?”

群友没回答出来,我只能说平时只在干体力活,CRUD 搬砖很认真,从来就没去认真的思考背后的实现原理。

正如我昨天文章中所说的:不是会搬砖就能修出港珠澳大桥

下面我抽 5 分钟,来简单解答一下!不喜轻喷!

一、原理

Spring 之所以可以对开启 @Transactional 的方法进行事务管理,是因为 Spring 为当前类生成了一个代理类,然后在执行相关方法时,会判断这个方法有没有 @Transactional 注解,如果有的话,则会开启一个事务。

也就是说我们首先调用的是 AOP 代理对象而不是目标对象。首先执行事务切面,事务切面内部通过 TransactionInterceptor 环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务。

Spring 在扫描 bean 的时候,如果扫描到方法上有这些注解,那么 spring 会通过动态代理模式,为这个 bean 动态地生成一个代理类,在代理类中,会对有注解的这个方法,做一些增强处理,如给有 @Transactional 注解的方法开启 transaction。

当我们想要调用这个方法时,「实际上是先调用了代理对象中被增强的方法,然后在代理对象中,又会调用我们实际的目标对象中的方法」。在通过代理对象中转的这一过程中,像上边说的开启和提交 transaction 就实现了。

二、代码模拟

实现机制我们大体知道了,下面我们用代码来模拟演示一下。

假设我们订单业务处理类中,有两个方法:校验订单参数方法verifyOrderParameters()和保存订单方法saveOrder(),其中saveOrder方法上加了@Transactional注解,verifyOrderParameters方法内会调用saveOrder方法。

OrderService

/**
 * 订单业务层的接口定义
 */

public interface OrderService {
 
    /**
     * 校验订单参数
     */

    void verifyOrderParameters();
 
    /**
     * 保存订单
     */

    void saveOrder();
}

OrderServiceImpl

/**
 * 订单业务层的具体处理类
 */

public class OrderServiceImpl implements OrderService{
 
    @Override
    public void verifyOrderParameters() {
        System.out.println("校验订单参数");
        // 调用保存订单方法
        saveOrder();
    }
 
    @Override
    @Transactional
    public void saveOrder() {
        System.out.println("保存订单信息到DB");
    }
}

我们用伪代码来演示一下 Spring 动态代理生成的代理类。

OrderServiceImplProxy

/**
 * 订单业务层具体处理类的代理类
 */

public class OrderServiceImplProxy implements OrderService{
 
    /**
     * 持有被代理的具体的目标对象
     */

    private OrderServiceImpl orderServiceImpl;
 
    public OrderServiceImplProxy(OrderServiceImpl orderServiceImpl) {
        this.orderServiceImpl = orderServiceImpl;
    }
 
    @Override
    public void verifyOrderParameters() {
        orderServiceImpl.verifyOrderParameters();
    }
 
    @Override
    public void saveOrder() {
        System.out.println("开启事务。。。");
        orderServiceImpl.saveOrder();
        System.out.println("提交事务。。。");
    }
}

这边我们来看下客户端调用 saveOrder 方法和 verifyOrderParameters 方法,输出结果都是什么样的。

public class Client {
 
    public static void main(String[] args) {
        // 创建一个订单业务的真实处理对象
        OrderServiceImpl orderServiceImpl = new OrderServiceImpl();
        // 创建一个代理对象
        OrderServiceImplProxy orderServiceImplProxy = new OrderServiceImplProxy(orderServiceImpl);
        // 执行代理对象的校验订单方法
        orderServiceImplProxy.verifyOrderParameters();
        System.out.println("--------------------------------------------");
        // 执行代理对象的保存订单方法
        orderServiceImplProxy.saveOrder();
    }
}

执行 main 方法后,得到下边的输出:

校验订单参数
保存订单信息到DB
--------------------------------------------
开启事务。。。
保存订单信息到DB
提交事务。。。

所以,普通方法verifyOrderParameters内调用注解方法saveOrder时,其实调用的是原目标对象(orderServiceImpl)的saveOrder方法,没有走代理对象(orderServiceImplProxy)中被增强的saveOrder方法,所以就不会产生效果啦。

三、解决方案

「方法1:」

将事务方法放到另一个类中进行调用。

即错误用法:

@Service
public class A {
  public void a(){
    // 省略其他业务代码
    b();
  }
  
  @Transactional
  public void b(){
  
  }
}

正确用法:

@Service
public class A {
  @Resource
  private B b;
  
  public void a(){
    // 省略其他业务代码
    b.b();
  }
}

@Service
public class B {
  @Transactional
  public void b(){
  
  }
}

「方法2:」

获取本对象的代理对象,再进行调用。具体操作如:

@Service
public class OrderService {
 
  private void insert() {
    insertOrder();
  }

  @Transactional
  public void insertOrder() {
    //SQL操作
  }
}

变更为:

@Service
public class OrderService {
  public void insert() {
    OrderService proxy = (OrderService) AopContext.currentProxy();
     proxy.insertOrder();
  }

  @Transactional
  public void insertOrder() {
      //SQL操作
  }
}

「方法3:」

利用非 this 调用。具体操作如:

@Service
public class OrderService {
  public void insert() {
    SpringUtil.getBean(OrderService.class).insertOrder();
    // SpringUtil 获取 bean 对象
  }

  @Transactional
  public void insertOrder() {
      //SQL操作
  }
}

以上内容,希望能够对大家有所帮助!内容不深,但是微信群里不少网友在面试中遇到了还不会回答。送分题,多可惜!

good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter