Spring 事务不能回滚之深层次原因——暴露代理对象exposeProxy属性的应用

缘起

有的时候,事务并不能像我们想的那样回滚. 如下例

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
这里要实现的需求是
* 1. insert_parent必须调用insert_child
* 2. insert_child不是太重要,也就是insert_child成功与否对insert_parent是没有影响的
*/
@Transactional(propagation = Propagation.REQUIRED)
public void insert_parent() { // 父事务方法
userDao.insert("parent");
try {
insert_child();
} catch (Exception e) {
e.printStackTrace();
}
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insert_child() { // 子事务方法
userDao.insert("child");
int i = 1 / 0;
}

但是实际效果却是: 父子方法都插入成功. 并没有因为算数异常而子事务回滚. 这是为什么呢?

读过Spring 事务源码的童鞋都知道——Spring事务基于动态代理(JDK或者Cglib),对于方法内部调用,其实调用的不是代理的方法,而是目标的方法,自然不会开启事务啦.

这其实是Spring事务给开发者留的一个坑.

那怎么办呢? 下面这样办

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Transactional(propagation = Propagation.REQUIRED)
public void insert_parent() {
System.out.println(this.getClass().getName()); // com.yfs.service.UserService 注意,不会打印出代理类,而是被代理对象
userDao.insert("parent");
try {
((UserService) AopContext.currentProxy()).insert_child(); // 通过AopContext从当前线程获取代理对象调用事务方法就会走事务代理,
// 则此时insert_child上的@Transactional注解就生效了
} catch (Exception e) {
e.printStackTrace();
}
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insert_child() {
userDao.insert("child");
int i = 1 / 0;
}

并且在配置类上多加一个注解

1
@EnableAspectJAutoProxy(exposeProxy = true)

具体代码参见【1】.

那么原理是什么呢?

首先我们来看看EnableAspectJAutoProxy注解的exposeProxy属性上的注释

1
Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal} for retrieval via the {@link org.springframework.aop.framework.AopContext} class.

看到这里心理基本就有了底. 就约莫知道,这是解决之道.

但是为什么可以成功呢?

我们来到【2】中搜索 “所以要记得” 这5个字,则知道了拦截器栈中的加入的DynamicAdvisedInterceptor这个开启责任链的拦截器中的advised属性是一个带有exposeProxy属性的org.springframework.aop.framework.ProxyFactory.

我们来回顾DynamicAdvisedInterceptor的开启责任链的intercept方法(其实就是【2】的源码33,只是这里和那里关心的问题不一样而已)

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
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) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
...

​ 源码3

注意第8行的判断,如果它是true的话,就会将我们造好的代理放进AopContex中去. 所以问题的关键就是DynamicAdvisedInterceptor的advised属性的exposeProxy是不是true.

而我们说了,DynamicAdvisedInterceptor的advised属性是在【2】的源码27的第17行的createProxy. 而跟进去就是【2】的源码28. 注意第9行——proxyFactory.copyFrom(this);,这里的proxyFactory就是最后DynamicAdvisedInterceptor中的advised.

而this就是AnnotationAwareAspectJAutoProxyCreator. 而这个bean根据【2】,是@EnableAspectJAutoProxy 注解引入的. 而是AspectJAutoProxyRegistrar.registerBeanDefinitions引入的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}

​ 源码1

注意,在【2】中我们只关心了第五行,但其实我们想说的是第12行, 而这里的enableAspectJAutoProxy根据第7行就是importingClass= com.yfs.config.MainConfig 上的@EnableAspectJAutoProxy注解信息. 我们在【1】中的MainConfig上写了

1
@EnableAspectJAutoProxy(exposeProxy = true)

所以这里当然可以提取到exposeProxy属性为true. 所以就会走源码1的第13行. 跟进去

org.springframework.aop.config.AopConfigUtils.forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry)

1
2
3
4
5
6
public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
}
}

​ 源码2

注意源码2的第四行,definition就是org.springframework.aop.config.internalAutoProxyCreator这个bean的定义信息, 注意,根据【2】,org.springframework.aop.config.internalAutoProxyCreator这个bean就是AnnotationAwareAspectJAutoProxyCreator. 即definition就是AnnotationAwareAspectJAutoProxyCreator的bean定义信息,然后往里面加了exposeProxy=true的属性. 后面在refresh方法中的registerBeanPostProcessors(beanFactory); 方法里面初始化这个bean的时候就会根据这个属性加上,所以

AnnotationAwareAspectJAutoProxyCreator中的exposeProxy属性就是true. 所以源码3的第10行就会将创建的代理放进AopContext中去. 方便后面取出来(也是只取当前线程的).

参考

【1】https://github.com/yfsyfs/backend/tree/master/spring-annotation-transaction-child-parent-correct

【2】https://yfsyfs.github.io/2019/06/13/spring-AOP-%E6%B3%A8%E8%A7%A3%E5%8E%9F%E7%90%86/