Spring AOP + Transactional源碼解析

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;
}

先看一下生成代理對象的時序圖


20200827145310512_991703414.png

檢測是否要代理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;
}
  • 檢測返回的可用的增強器(AdvisorAdvice)列表后,如果列表不為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
    • 指定的目標類為接口類型
    • 當且僅當使用newProxyInstancegetProxyClass動態(tài)將目標類生成代理類時
  • 使用Cglib代理
    • proxyTargetClass(是否直接代理目標類)為true
    • 指定的目標類不為接口類型
    • 未使用newProxyInstancegetProxyClass動態(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代理

20200827145333136_1411722303.png

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()是攔截的首入口。
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代理

20200827171028608_1306162524.png

JDK代理是委托給JdkDynamicAopProxy去創(chuàng)建的,創(chuàng)建過程如下

  • 設(shè)置要實現(xiàn)的接口,有則使用目標類的接口,默認還會實現(xiàn)SpringProxyAdvised接口
  • 查找目標類是否定義了equalshashcode,是的話分別標記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í)行攔截器;
20200828124511456_281569522.png

事務(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ù)的傳播機制
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容