准确描述: 同一个对象内的嵌套方法调用 Spring AOP拦截失效
原理参考:《Spring 揭秘》 12.1 有关公开当前调用的代理对象的探讨
《Spring 揭秘》12.1 读书笔记
问题代码示例
public interface AService {
public void a();
public void b();
}
@Service
public class AServiceImpl implements AService{
@Transactional(propagation = Propagation.REQUIRED)
public void a() {
this.b();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
}
}
观察现象
直接现象
b方法事务注解失效
调试现象
使用如下 Step Into
按钮,来调试。
@PostMapping("/a")
public void test() {
aService.a();
}
可以看到,我们调用a方法的调用栈,是进入了CglibAopProxy当中。这个事务是生效的
再看看b方法的调用:直接进入到了实际方法,而没有经过CglibAopProxy
透过现象看本质
b方法中的 this
指向目标对象,并非代理对象。因此调用 this.b()
将不会执行b方法的事务切面,即不会执行事务增强,因此b方法的事务定义@Transactional(propagation = Propagation.REQUIRES_NEW)
将不会实施.
解决方案
- 使用
expose-proxy
属性来暴露代理对象后,使用以下代码调用,这样在调试后,便进入了CglibAopProxy当中。(这种方案需要开启AspectJ)
AService aService = (AService) AopContext.currentProxy();
aService.b();
方案原理
参考 CglibAopProxy#intercept
方法
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
finally {
// ...
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
- 在实际项目中,大多情况下建议在
controller
直接调用的service 方法,(在本示例中为a方法)开启一个完整的物理事务,并在调用的子方法上,也标注事务(如本示例中的b方法)。