你知道的越多,不知道的就越多,业余的像一棵小草!
编辑:业余草
今年面试已经很难了,但是还有网友还不上心。今天群里有一位网友,获得了一个面试机会。但是他没抓住,非常可惜。
面试官问了一个送分题:“@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操作
}
}
以上内容,希望能够对大家有所帮助!内容不深,但是微信群里不少网友在面试中遇到了还不会回答。送分题,多可惜!