Spring AOP應(yīng)用于多數(shù)場景
- 緩存
- 權(quán)限
- 懶加載
- 日志
- 事務(wù)
- 。。。
這一篇將通過AOP源碼的實現(xiàn)層面,結(jié)合事務(wù)的傳播機制,來理解AOP是如何管理事務(wù)的。
生成AopProxy代理
Spring在啟動期間,會將待注入的類注入到容器中,期間它會判斷該類是否需要被代理,是的話將會創(chuàng)建該類實例的代理對象,代碼片段如下
方法位于org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
先看一下生成代理對象的時序圖

檢測是否要代理bean(AbstractAutoProxyCreator#wrapIfNecessary)
- 檢查是否有必要包裝一下Bean,即是否需要往bean上添加代理對象,具體的檢測邏輯如下
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 獲取要在自動代理中使用的候選顧問(增強器,由Advisor和Advice組成)列表
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 過濾得到合格的候選顧問列表
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
- 檢測返回的可用的增強器(
Advisor和Advice)列表后,如果列表不為null,則將其作為被代理的bean的狀態(tài)緩存起來,并且開始創(chuàng)建代理對象
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
// 緩存當前bean的代理狀態(tài)
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
// 緩存代理類實例
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
創(chuàng)建代理工廠,通過工廠創(chuàng)建bean的代理對象
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
// 創(chuàng)建代理工廠,并且復制當前實例的相關(guān)屬性
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
// 是否設(shè)置直接代理目標類,而不是目標類接口
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 根據(jù)可用的增強器列表構(gòu)建真正適用于該bean的增強器列表
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
// 設(shè)置目標代理類
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(getProxyClassLoader());
}
代理工廠選取合適的AOP代理(ProxyFactory#getProxy)
上面創(chuàng)建的工廠代理類,配置了相應(yīng)的屬性后,將選取合適的AOP代理,并且生成代理對象
創(chuàng)建AOP代理
protected final synchronized AopProxy createAopProxy() {
// 如果代理未激活,則激活該代理配置
if (!this.active) {
activate();
}
// 獲取aop代理工廠類,并創(chuàng)建aop代理
return getAopProxyFactory().createAopProxy(this);
}
Spring內(nèi)置的aop代理有兩種:JDK和Cglib。之前創(chuàng)建的代理工廠在下面創(chuàng)建方法作為形參
- 使用JDK動態(tài)代理
- proxyTargetClass(是否直接代理目標類)為false
- 指定的目標類為接口類型
- 當且僅當使用
newProxyInstance或getProxyClass動態(tài)將目標類生成代理類時
- 使用Cglib代理
- proxyTargetClass(是否直接代理目標類)為true
- 指定的目標類不為接口類型
- 未使用
newProxyInstance或getProxyClass動態(tài)將目標類生成代理類時
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
- 在Spring5中,AOP默認還是用JDK動態(tài)代理,如果被代理類未實現(xiàn)接口才使用Cglib代理
- 而在Springboot2.x中,AOP已默認使用Cglib代理
即被代理類有沒有實現(xiàn)接口,它都使用Cglib代理
那如何更改為JDK代理呢?在Springboot中都是依靠自動裝配來實現(xiàn)的,在啟動過程中會加載各種配置,其中就涉及了AOP相關(guān)的配置,通過spring.factories可知相關(guān)配置代碼在org.springframework.boot.autoconfigure.aop.AopAutoConfiguration。
從代碼得知,Springboot2.x默認使用Cglib代理在于配置了spring.aop.proxy-target-class=true,故要切為JDK代理可配置spring.aop.proxy-target-class=false
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {
ClassProxyingConfiguration(BeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
}
}
}
使用類加載器創(chuàng)建代理對象
類加載器一般使用應(yīng)用類加載器AppClassLoader,也可以自己實現(xiàn)了自定義加載器。
Cglib代理

Cglib代理是委托給CglibAopProxy去創(chuàng)建的,創(chuàng)建過程如下
- 獲得目標類
rootClass - 如果目標類已經(jīng)被代理,則獲取它的父類作為目標類
- 驗證目標類,檢查是否需要寫日志
- 創(chuàng)建Enhancer對象(即代理增強器,類比于jdk自帶的Proxy),準備創(chuàng)建代理類
- 設(shè)置目標類為代理增強器父類
- 設(shè)置要實現(xiàn)的接口,有則使用目標類的接口,默認還會實現(xiàn)
SpringProxy,Advised接口 - 覆蓋命名策略,一般生成的代理類都是有對應(yīng)的命名策略,在spring中,命名規(guī)范是:目標類名+
$$EnhancerBySpringCGLIB - 設(shè)置生成字節(jié)碼的策略,默認使用
DefaultGeneratorStrategy,Spring中用了GeneratorStrategy的另一種實現(xiàn)ClassLoaderAwareGeneratorStrategy - 設(shè)置回調(diào)過濾器,根據(jù)被攔截的方法執(zhí)行不同的處理邏輯。
CallbackFilter#accpet在實際調(diào)用中,會根據(jù)被攔截的方法返回對應(yīng)的索引,在Callback中會根據(jù)索引值拿到對應(yīng)的回調(diào)處理邏輯
- 創(chuàng)建目標類的代理類和目標類的代理類實例
- 創(chuàng)建代理類
proxyClass - 創(chuàng)建代理類實例
proxyInstance - 設(shè)置回調(diào)組
Callbacks,和CallbackFilter結(jié)合使用,回調(diào)組中有一個通用回調(diào)處理器DynamicAdvisedInterceptor,其intercept()是攔截的首入口。
- 創(chuàng)建代理類
public Object getProxy(@Nullable ClassLoader classLoader) {
try {
Class<?> rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
Class<?> proxySuperClass = rootClass;
if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
proxySuperClass = rootClass.getSuperclass();
// ...
}
validateClassIfNecessary(proxySuperClass, classLoader);
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException | IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
": Common causes of this problem include using a final class or a non-visible class",
ex);
}
catch (Throwable ex) {
throw new AopConfigException("Unexpected AOP exception", ex);
}
}
JDK代理

JDK代理是委托給JdkDynamicAopProxy去創(chuàng)建的,創(chuàng)建過程如下
- 設(shè)置要實現(xiàn)的接口,有則使用目標類的接口,默認還會實現(xiàn)
SpringProxy,Advised接口 - 查找目標類是否定義了
equals和hashcode,是的話分別標記equalsDefined為true,hashCodeDefined為true - 調(diào)用
Proxy.newProxyInstance生成目標類的代理類實例
public Object getProxy(@Nullable ClassLoader classLoader) {
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
事務(wù)傳播機制
在Spring中,事務(wù)機制與AOP做了結(jié)合,通過AOP封裝了事務(wù)管理的代碼,接下來將會通過AOP理解事務(wù)的傳播機制。
Propagation七種傳播機制
相關(guān)枚舉在org.springframework.transaction.annotation.Propagation中
| 機制 | 說明 | 場景 |
|---|---|---|
| PROPAGATION_REQUIRED | 若線程上下文中存在事務(wù)則加入,不存在則創(chuàng)建一個新事務(wù) | 最常使用,因為它是Spring缺省的傳播機制 |
| PROPAGATION_SUPPORTS | 若線程上下文中存在事務(wù)則加入,不存在則以非事務(wù)的模式執(zhí)行 | -(使用場景較少) |
| PROPAGATION_MANDATORY | 若線程上下文中存在事務(wù)則加入,不存在則拋出異常 | 常用于檢測調(diào)用代碼的上下文是否存在事務(wù),提醒必須以事務(wù)運行 |
| PROPAGATION_REQUIRES_NEW | 若線程上下文中存在事務(wù)則暫時掛起****,并創(chuàng)建一個新事務(wù),執(zhí)行完后才恢復其他上下文事務(wù) | 常用于內(nèi)層事務(wù)異常會導致回滾時,外層事務(wù)(一般)不會被回滾 |
| PROPAGATION_NOT_SUPPORTED | 若線程上下文中存在事務(wù)則掛起,并以非事務(wù)的模式執(zhí)行,執(zhí)行完才恢復上下文事務(wù) | 常用于減少事務(wù)范圍,同時避免內(nèi)層事務(wù)異常而導致不必要的全局回滾的場景 |
| PROPAGATION_NEVER | 若線程上下文中存在事務(wù),則拋出異常 | -(使用場景較少) |
| PROPAGATION_NESTED | 若線程上下文中存在則嵌套事務(wù)執(zhí)行,不存在則創(chuàng)建一個新事務(wù)。 它僅支持“在特定的事務(wù)管理器 DataSourceTransactionManager上,以及使用jdbc3.0驅(qū)動程序”的使用,其提供了一個“save point”的父子事務(wù)的概念,在進入子事務(wù)之前建立一個“save point”,當子事務(wù)回滾時會先滾到“save point”,而父事務(wù)可以選擇性回滾。 |
-(使用場景較少) |
實踐
關(guān)于傳播機制,在上面已經(jīng)做了描述,然而事實真的如以上描述一般的簡單嗎,接下來模擬做些測試用例,看看結(jié)論。
首先,先清楚兩個點
- Spring默認傳播機制是
PROPAGATION_REQUIRED -
@Transactional中默認回滾異常是RuntimeException,但是p3c建議我們顯示的rollback
場景1:兩個service A/B, A方法調(diào)用B方法且都開啟了事務(wù)(默認級別Propagation.REQUIRED),B方法拋出Runtime異常
@Transactional(rollbackFor = Exception.class)
public void addUser() {
userMapper.insert(User.create("Jerry", 21));
userChannelService.addUserChannel();
}
@Transactional(rollbackFor = Exception.class)
public void addUserChannel() {
throw new NullPointerException("在不同service內(nèi)假裝拋出Runtime異常");
}
場景2:兩個service A/B, A方法調(diào)用B方法且都開啟了事務(wù)(默認級別Propagation.REQUIRED),B方法拋出Runtime異常但是進行了異常捕獲
@Transactional(rollbackFor = Exception.class)
public void addUserV2() {
userMapper.insert(User.create("Jerry", 22));
userChannelService.addUserChannelV2();
}
@Transactional(rollbackFor = Exception.class)
public void addUserChannelV2() {
try {
throw new NullPointerException("在不同service內(nèi)假裝拋出Runtime異常并捕獲");
} catch (NullPointerException e) {
}
}
以上是在內(nèi)層事務(wù)捕獲,同樣考慮一下在外層事務(wù)捕獲的結(jié)果
場景3:兩個service A/B, A方法調(diào)用B方法且都開啟了事務(wù),B方法拋出Runtime異常但是進行了異常捕獲,且B方法事務(wù)指定了隔離級別為Propagation.REQUIRES_NEW
@Transactional(rollbackFor = Exception.class)
public void addUserV3() {
userMapper.insert(User.create("Jerry", 23));
userChannelService.addUserChannelV3();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addUserChannelV3() {
try {
userChannelMapper.insert(UserChannel.create(1, "WX", "111"));
throw new NullPointerException("在不同service內(nèi)假裝拋出Runtime異常并捕獲,但是我設(shè)置了隔離級別為新建事務(wù)");
} catch (NullPointerException e) {
e.printStackTrace();
}
}
以上是在內(nèi)層事務(wù)捕獲,同樣考慮一下在外層事務(wù)捕獲的結(jié)果
場景4:兩個service A/B, A方法調(diào)用B方法且都開啟了事務(wù),B方法拋出Runtime異常,且B方法事務(wù)指定了隔離級別為Propagation.REQUIRES_NEW
@Transactional(rollbackFor = Exception.class)
public void addUserV4() {
userMapper.insert(User.create("Jerry", 24));
userChannelService.addUserChannelV4();
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void addUserChannelV4() {
throw new NullPointerException("在不同service內(nèi)假裝拋出Runtime異常,但是我設(shè)置了隔離級別為新建事務(wù)");
}
場景5:兩個service A/B, A方法調(diào)用B方法且都開啟了事務(wù)(默認級別Propagation.REQUIRED),B方法拋出自定義異常(非Runtime)
@Transactional(rollbackFor = RuntimeException.class)
public void addUserV5() throws CustomException {
userMapper.insert(User.create("Jerry", 25));
userChannelService.addUserChannelV5();
}
@Transactional(rollbackFor = RuntimeException.class)
public void addUserChannelV5() throws CustomException {
throw new CustomException("在不同service內(nèi)假裝拋出自定義異常");
}
以上指定回滾異常為RuntimeException,要是設(shè)置為Exception,會是什么結(jié)果呢?
場景6:兩個service A/B, A方法調(diào)用B方法且都開啟了事務(wù)(默認級別Propagation.REQUIRED),B方法拋出自定義異常(非Runtime)但是進行了異常捕獲
@Transactional(rollbackFor = RuntimeException.class)
public void addUserV6() {
userMapper.insert(User.create("Jerry", 26));
userChannelService.addUserChannelV6();
}
@Transactional(rollbackFor = RuntimeException.class)
public void addUserChannelV6() {
try {
userChannelMapper.insert(UserChannel.create(1, "WX", "111"));
throw new CustomException("在不同service內(nèi)假裝拋出自定義異常并捕獲");
} catch (CustomException e) {
e.printStackTrace();
}
}
以上指定回滾異常為RuntimeException,要是設(shè)置為Exception,會是什么結(jié)果呢?
場景7:service A, a方法調(diào)用內(nèi)部方法b且都開啟了事務(wù)(默認級別Propagation.REQUIRED),b方法拋出Runtime異常
@Transactional(rollbackFor = Exception.class)
public void addUserV7() {
userMapper.insert(User.create("Jerry", 27));
exception();
}
@Transactional(rollbackFor = Exception.class)
public void exception() {
throw new NullPointerException("在同個service內(nèi)假裝拋出異常");
}
場景8:service A, a方法調(diào)用內(nèi)部方法b且都開啟了事務(wù)(默認級別Propagation.REQUIRED),b方法拋出Runtime異常,且c方法事務(wù)指定了隔離級別為Propagation.REQUIRES_NEW
@Transactional(rollbackFor = Exception.class)
public void addUserV8() {
userMapper.insert(User.create("Jerry", 28));
exceptionV2();
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void exceptionV2() {
throw new NullPointerException("在同個service內(nèi)假裝拋出異常,但是我設(shè)置了隔離級別為新建事務(wù),也會回滾");
}
AopProxy代理事務(wù)的操作過程
上面提供了8個場景,在獲取測試結(jié)果之前,先看下AOP代理事務(wù)的操作流程
Cglib代理過程
同樣,Cglib代理過程也是委托給CglibAopProxy去執(zhí)行的,那結(jié)合上面創(chuàng)建代理對象的流程,我們?nèi)绾蔚弥韴?zhí)行的入口和代理流程呢?
在Cglib代理對象的創(chuàng)建過程中設(shè)置的回調(diào)組Callbacks中,回調(diào)類如下
Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
Callback[] mainCallbacks = new Callback[] {
aopInterceptor, // for normal advice
targetInterceptor, // invoke target without considering advice, if optimized
new SerializableNoOp(), // no override for methods mapped to this
targetDispatcher, this.advisedDispatcher,
new EqualsInterceptor(this.advised),
new HashCodeInterceptor(this.advised)
};
結(jié)合前面提到的,它一般和CallbackFilter結(jié)合使用,CglibAopProxy#accpet在實際調(diào)用中,會根據(jù)被攔截的方法返回對應(yīng)的索引,在Callback中會根據(jù)索引值拿到對應(yīng)的回調(diào)處理邏輯,其實CglibAopProxy中已經(jīng)聲明了對應(yīng)靜態(tài)不可變的成員變量
private static final int AOP_PROXY = 0;
private static final int INVOKE_TARGET = 1;
private static final int NO_OVERRIDE = 2;
private static final int DISPATCH_TARGET = 3;
private static final int DISPATCH_ADVISED = 4;
private static final int INVOKE_EQUALS = 5;
private static final int INVOKE_HASHCODE = 6;
在org.springframework.cglib.proxy.Enhancer#emitMethods中,會遍歷被代理類的方法,并且設(shè)置切入點,初始默認會返回AOP_PROXY,即回調(diào)處理邏輯將在通用AOP回調(diào)DynamicAdvisedInterceptor#intercept中進行,具體看源碼即可
好了,知道了代理的入口,再整理下代理流程
- 調(diào)用方法
method,首先攔截于DynamicAdvisedInterceptor#intercept; - 獲取目標類實例
targetClass,根據(jù)method+targetClass從代理配置管理器AdvisedSupport中獲取攔截器(Advice通知)鏈條chain; - 創(chuàng)建一個Cglib方法調(diào)用對象
CglibMethodInvocation,執(zhí)行調(diào)用方法proceed,準備調(diào)用攔截器鏈條 -
CglibMethodInvocation中維護著當前攔截器索引currentInterceptorIndex,當它小于攔截器鏈條長度時,會自增并依次作為索引條件獲取指定攔截器,并執(zhí)行它的切入點TransactionInterceptor#invoke(),從名字可以看出是一個事務(wù)操作的攔截器,當自增到等于攔截器鏈條長度時,開始真正調(diào)用被代理方法; - 接下來會執(zhí)行
TransactionAspectSupport#invokeWithinTransaction,在被代理方法前先開啟事務(wù),接著回調(diào)到上一步,循環(huán)執(zhí)行攔截器;

事務(wù)執(zhí)行過程
在Spring事務(wù)執(zhí)行中,它提供了抽象事務(wù)管理器類
AbstractPlatformTransactionManager來處理通用的事務(wù)處理流程,而它的子類將做具體的實現(xiàn),如事務(wù)開始,事務(wù)提交、事務(wù)回滾等,如DataSourceTransactionManager、JpaTransactionManager等。
上面代理執(zhí)行過程中,在事務(wù)攔截器階段,將會執(zhí)行一段事務(wù)操作,其中最核心的過程和代碼片段如下
- 開啟事務(wù)
- 執(zhí)行被代理的業(yè)務(wù)方法
- 異常則回滾事務(wù)
- 提交事務(wù)
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//...
commitTransactionAfterReturning(txInfo);
return retVal;
}
執(zhí)行過程很清晰,值得注意的是,在completeTransactionAfterThrowing中,并不一定直接回滾,如果發(fā)現(xiàn)當前異常非指定的rollback異常,則會直接commit(),再將異常拋到外部,你覺得此 commit()真的會成功嗎?看下面場景5的結(jié)果吧!
再看下回滾事務(wù)的代碼,位于抽象事務(wù)管理器AbstractPlatformTransactionManager#processRollback,有下面幾種回滾原因
- 原因一:如果事務(wù)設(shè)置了“save point”,即使用了
PROPAGATION_NESTED嵌套事務(wù)的傳播機制,則回滾到“save point”
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
- 原因二:如果事務(wù)是新開事務(wù)(單一事務(wù)也算),則直接讓該事務(wù)回滾
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
- 原因三:如果當前事務(wù)有回滾標志,或者當前事務(wù)開啟了全局事務(wù)回滾
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
- 原因四:以上幾種都屬于上述步驟的第三步:異常則回滾事務(wù),即在
commit()之前就導致的異?;貪L,而當在請求commit()時檢測到全局事務(wù)被標記了rollback-only,這是一種不期望的行為,也會導致回滾
commit()過程的回滾代碼,位于AbstractPlatformTransactionManager#commit
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
在AbstractPlatformTransactionManager#processRollback中,會直接打印出錯誤信息:不期望的回滾異常
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
由于這里使用的具體實現(xiàn)是DataSourceTransactionManager,故實際的回滾操作doRollback、doSetRollbackOnly將會在其中操作。
結(jié)合代理操作過程得出實踐場景結(jié)果
場景1(默認級別Propagation.REQUIRED)
結(jié)果:A/B都會回滾(從上一步分析得知,這里屬于原因三的回滾行為)
總結(jié):A類方法調(diào)用B類方法,且都開啟事務(wù),若B類方法拋出Runtime異常,則B方法會回滾,而因為和A方法處于同一事務(wù),也導致A方法會回滾。
場景2(默認級別Propagation.REQUIRED)
-
結(jié)果:
- 在A方法捕獲:A、B會回滾(從上一步分析得知,這里屬于原因四的回滾行為)
- 在B方法捕獲:A、B不會回滾
-
總結(jié):
- A類方法調(diào)用B類方法,且都開啟事務(wù),若B類方法拋出Runtime異常,并在外層A方法捕獲,則B方法會回滾,而因為和A方法的同一事務(wù),也導致A方法會回滾(不管A方法是否有做捕獲操作),此時會拋出"Transaction rolled back because it has been marked as rollback-only"
- A類方法調(diào)用B類方法,且都開啟事務(wù),若B類方法拋出Runtime異常,并在內(nèi)層捕獲,則B方法不會回滾,A方法也不會回滾
場景3(A:默認級別Propagation.REQUIRED,B:Propagation.REQUIRES_NEW)
-
結(jié)果:
- A方法一定不會回滾
- 在A方法捕獲:B會回滾(從上一步分析得知,這里屬于原因二的回滾行為)
- 在B方法捕獲:B不會回滾
-
總結(jié):
- A類方法調(diào)用B類方法,且都開啟事務(wù),若B類方法拋出Runtime異常,并在外層A方法捕獲,則B方法會回滾,但是因為和A方法不是同一事務(wù),所以A方法不會回滾
- A類方法調(diào)用B類方法,且都開啟事務(wù),若B類方法拋出Runtime異常,并在內(nèi)層B方法捕獲,則B方法不會回滾,A方法也不會回滾
場景4(A:默認級別Propagation.REQUIRED,B:Propagation.REQUIRES_NEW)
- 結(jié)果:A/B方法都會回滾(從上一步分析得知,這里B方法的回滾屬于原因二的回滾行為)
可能有些人會有疑問,我已經(jīng)將B方法設(shè)置為開啟事務(wù),怎么還會導致A方法回滾呢?
很簡單,從A方法看起,它指定回滾的異常是Exception,而B方法拋出了NPE異常后又沒有捕獲,所以盡管B方法開啟了新的事務(wù),但是是由A發(fā)起的且拋出NPE異常,所以A方法也回滾了。
故可以換另一種角度,當A方法指定回滾異常是RuntimeException,而B方法回滾異常為非Runtime異常,此時可以發(fā)現(xiàn)A方法并不會回滾!并且B方法指定回滾異常也為RuntimeException,則B方法也不會回滾!
- 總結(jié):A類方法調(diào)用B類方法,且都開啟事務(wù),若B類方法拋出Runtime異常,則A、B方法都會回滾
場景5(默認級別Propagation.REQUIRED)
- 結(jié)果:兩個方法同時指定回滾異常為RuntimeException,才不會回滾,否則都會回滾
在上面事務(wù)執(zhí)行過程中,講到了當前異常非rollback異常時會直接commit(),而此次commit()會不會成功呢?答案是不一定!
如果A/B都在同個事務(wù)中,并且內(nèi)層B方法指定異常為RuntimeException,而拋出異常為自定義異常,則會commit(),由于沒有捕獲異常,故無論外層A方法指定什么異常,A/B方法的業(yè)務(wù)操作都會一起被回滾?。◤纳弦徊椒治龅弥?,這里的回滾屬于原因二的回滾行為);那怎樣才能讓此次commit()成功呢,只要在內(nèi)層B方法中進行捕獲即可
- 總結(jié):
- A類方法(回滾異常為Exception)調(diào)用B類方法(回滾異常為RuntimeException),且都開啟事務(wù),若B類方法拋出自定義異常(非Runtime),則A、B都會回滾;
- A類方法(回滾異常為Exception)調(diào)用B類方法(回滾異常為Exception),且都開啟事務(wù),若B類方法拋出自定義異常(非Runtime),則A、B都會回滾;
- A類方法(回滾異常為RuntimeException)調(diào)用B類方法(回滾異常為Exception),且都開啟事務(wù),若B類方法拋出自定義異常(非Runtime),則A、B都會回滾;
- A類方法(回滾異常為RuntimeException)調(diào)用B類方法(回滾異常為RuntimeException),且都開啟事務(wù),若B類方法拋出自定義異常(非Runtime),則B會回滾,A不會回滾
場景6(默認級別Propagation.REQUIRED)
- 結(jié)果:不會回滾
- 總結(jié):A類方法調(diào)用B類方法,且都開啟事務(wù),若B類方法拋出自定義異常并且捕獲,則不會回滾
場景7(默認級別Propagation.REQUIRED)
- 結(jié)果:會回滾(從上一步分析得知,這里的回滾屬于原因二的回滾行為)
- 總結(jié):在同個類中a方法調(diào)用b方法,b方法不會開啟新事務(wù),即不會用到事務(wù)的傳播機制
場景8(默認級別Propagation.REQUIRED)
- 結(jié)果:會回滾(從上一步分析得知,這里的回滾屬于原因二的回滾行為)
- 總結(jié):在同個類中a方法調(diào)用b方法,b方法無論設(shè)置什么都不會開啟新事務(wù),即不會用到事務(wù)的傳播機制