参考:
- https://segmentfault.com/a/1190000008379179
- https://blog.csdn.net/aya19880214/article/details/50640596
一、概述
用过Spring声明式事务的小伙伴肯定都知道,有这种一种场景:
- 一个类中有两个方法testA()、testB(),testA()没有使用事务,testB()使用事务(默认传播机制为
REQUIRED :支持当前事务,如果不存在则创建一个新事务)
- 场景1:通过 service#testB() 方式调用:那么testB()是有事务的,默认传播机制的原因
- 场景2:通过 service#testA() -> testA()内部 this.testB() 调用:那么testB()是没有事务的
上面2个场景的结果大家都是知道的,但是仔细想了下就有点蒙了。
Spring的声明式事务是通过Spring AOP代理实现的,默认情况下接口通过JDK代理实现、普通类通过CGLIB代理实现。
通常,我们理解的CGLIB代理,相当于就是通过一个子类在进行增强,那么testA()内部调用testB(),因为java多态的原因,也是调用子类(代理类)来完成,那么也是有增强的逻辑在的,那么为什么Sring中的CGLIB代理就不行,,也就是testB()的事务为什么不起作用?(我之前一直以为Spring AOP中的CGLIB代理和普通的CGLIB代理实现逻辑是一样的,,)
下面就带着这个问题来捋一捋Spring AOP中的CGLIB代理和我们认为的CGLIB有什么不同…
二、CGLIB代理
2.1 CGLIB代理示例
回顾下CGLIB代理生成代理类并调用目标方法流程。
执行下面的代码后,可以发现testA()内部调用testB()时,也触发了增强逻辑,也就是调用了代理类。控制台打印结果如下:
====== 执行方法开始:testA ======
====== 执行方法开始:testB ======
====== 执行方法结束:testB ======
====== 执行方法结束:testA ======
示例代码如下:
public class TestService {
public void testA() {
this.testB();
}
public void testB() {
}
}
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "doc");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestService.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(String.format("====== 执行方法开始:%s ======", method.getName()));
Object object = methodProxy.invokeSuper(o, objects);
System.out.println(String.format("====== 执行方法结束:%s ======", method.getName()));
return object;
}
});
TestService testService = (TestService) enhancer.create();
testService.testA();
}
2.2 CGLIB生成的代理类
2.1中通过System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "doc"); 保存了生成的代理类class文件,通过反编译看下代理类。
代理类继承了TestService,同时重写了testA()、testB()方法。
- 代理类调用testA()时,就会进入我们定义的匿名内部类 - 方法拦截器中:
MethodInterceptor#intercept() intercept() 方法中执行增强逻辑,并调用TestService#testA() testA() 方法中调用了testB() ,因为继承的关系,又会调用代理类的testB() ,之后的逻辑和testA() 一样
CGLIB代理类反编译后源码部分:
public class TestService$$EnhancerByCGLIB$$c4eb28c extends TestService implements Factory {
public final void testA() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$testA$0$Method, CGLIB$emptyArgs, CGLIB$testA$0$Proxy);
} else {
super.testA();
}
}
public final void testB() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$testB$1$Method, CGLIB$emptyArgs, CGLIB$testB$1$Proxy);
} else {
super.testB();
}
}
}
一开始我的疑问也在这里,自己通过CGLIB代理模拟了代码增强的逻辑,同一类中内部方法调用也触发了代理类增强,怎么Spring AOP中的CGLIB代理怎么不触发呢。。
三、Spring AOP
带着上面的疑问,通过debug观察一下代理类调用目标方法的流程。
3.1 示例1:testA()没有事务、testB()声明使用事务
(1)场景1:Controller#test()接口中只调用testA()方法
发现Controller中注入的不是service代理类实例,是service本身的实例,debug直接进入testA()方法,testA()调用testB()也是一样,不会触发代理逻辑
(2)场景2:Controller#test()接口中先调用testA()、再调用testB()
此时发现注入的service是CGLIB代理类的实例了,debug时进入org.springframework.aop.framework.CglibAopProxy#intercept() 中。
我们重点观察下这个方法到底干了啥,是否和我们2.2节中生成的CGLIB代理类逻辑一样:该方法主要流程:
- 获取目标类
- 查找目标方法、类的拦截器和动态代理拦截器
- 若无拦截器且是public方法:通过反射调用目标类的目标方法
通过查看源码,发现Spring AOP不是通过代理类来调用testA(),而是通过目标类进行反射调用,这样就可以解释了为什么testB()没有事务,因为不是直接通过代理类来完成调用的,也就无法触发代理逻辑了。
示例代码:
public class TransactionController {
@Autowired
private TransactionService transactionService;
@GetMapping("/test")
public void test() {
transactionService.testA();
}
}
@Service
public class TransactionService {
@Autowired
private TransactionDao transactionDao;
public void testA() {
transactionDao.insert(bean);
testA();
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB() {
transactionDao.insert(bean);
int i = 1 / 0;
}
}
CglibAopProxy#intercept()源码如下:
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
try {
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = getTarget();
if (target != null) {
targetClass = target.getClass();
}
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
} else {
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null) {
releaseTarget(target);
}
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
}
3.2 示例2:testA()、testB()都声明使用事务(REQUIRED、REQUIRES_NEW)
(1)场景1:Controller#test()接口中只调用testA()方法
- 预期:testA()启用一个事务,testB()新建一个事务,testA()中抛出异常,不影响testB()所在的事务,所以b可以正常保存,a不可以保存
- 结果:a、b都无法保存
示例代码:
public class TransactionService {
@Autowired
private TransactionDao transactionDao;
@Transactional(propagation = Propagation.REQUIRED)
public void testA() {
transactionDao.insert("a");
testB();
int i = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB() {
transactionDao.insert("b");
}
}
由上面示例中可以猜测下,testB()的事务失效了,所以testA()和testB()在同一个事务中,testB()报错造成整个事务回滚了。
debug时仍然是进入org.springframework.aop.framework.CglibAopProxy#intercept() 中,该方法主要流程示例1中也简单分析过,这里主要是找到了拦截器:
- 获取目标类
- 查找目标方法、类的拦截器和动态代理拦截器(例如事务就是:
TransactionInterceptor )
- 若有拦截器,创建一个
CglibMethodInvocation ,并调用父类ReflectiveMethodInvocation 的proceed() 进行处理
proceed() 方法中,第一次会进入TransactionInterceptor#invoke() 进行事务相关逻辑的处理- 当事务准备好后就会触发执行目标方法,又回到了
ReflectiveMethodInvocation#proceed() 方法中,第二次会执行CglibAopProxy#invokeJoinpoint() 方法,完成调用目标方法的操作 - 重点就在这个
invokeJoinpoint() 方法中,都是通过反射执行目标类的目标方法,而不是我之前所认为的代理类,所以就和3.1节 示例1的情况对应起来了,内部调用时事务确实是不起作用的
到这里就比较清晰了,最终都是通过 代理类 完成逻辑增强,目标类 完成方法调用的,而不是代理类直接完成的,所以testA()调用testB()是不会触发代理逻辑的,相当于一个普通方法,和testA()在同一个事务中。
相关Spring源码如下:
public Object proceed() throws Throwable {
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
} else {
return proceed();
}
} else {
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
protected Object invokeJoinpoint() throws Throwable {
if (this.publicMethod) {
return this.methodProxy.invoke(this.target, this.arguments);
} else {
return super.invokeJoinpoint();
}
}
四、解决方法
常见的解决方法:
- 通过代理类调用
- 注入自身
- 拆成两个类
- AopContext.currentProxy()获取代理类
- 使用编程式事务
4.1 注入自身/拆成两个类
既然内部调用不起作用,那就使用代理对象来调用:
- 方式一:通过在AService中注入自己 selfService,再用selfService#testB()
- 方式二:拆成两个类,如AService、BService,testA()中通过bService#testB()
方式一的示例代码如下:
public class TransactionService {
@Autowired
private TransactionDao transactionDao;
@Autowired
private TransactionService transactionService;
@Transactional(propagation = Propagation.REQUIRED)
public void testA() {
transactionDao.insert("a");
transactionService.testB();
int i = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB() {
transactionDao.insert("b");
}
}
4.2 @EnableAspectJAutoProxy
- SpringBoot上启动类上添加
@EnableAspectJAutoProxy(exposeProxy = true) 注解,同时设置exposeProxy=true - testA()中通过 (TransactionService) AopContext.currentProxy()获取代理类
- 通过代理类调用testB()
示例代码:
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
public class TransactionService {
@Autowired
private TransactionDao transactionDao;
@Transactional(propagation = Propagation.REQUIRED)
public void testA() {
transactionDao.insert("a");
TransactionService transactionService = (TransactionService) AopContext.currentProxy();
transactionService.testB();
int i = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB() {
transactionDao.insert("b");
}
}
五、总结
- 同一类中调用事务不起作用
- 若需要同一类中也起作用,那么需要通过代理类完成调用,才能触发增强逻辑
- Spring AOP中会生成两个类:目标类、目标类的代理类,通过代理类完成增强逻辑,目标类完成实际的调用(这种统一的处理方式有好有坏,像CGLIB这种代理方式是通过子类完成的,也被当做普通的代理类)
|