Spring系列.事務管理原理簡析

Spring的事務管理功能能讓我們非常簡單地進行事務管理。只需要進行簡單的兩步配置即可:

step1:開啟事務管理功能

@Configuration
//@EnableTransactionManagement註解有以下幾個屬性
//proxyTargetClass屬相:指定事務的AOP是通過JDK動態代理實現,還是CGLIB動態代理實現。true的話是CGLIB,false的話是JDK動態代理
//                     需要注意的是這個屬性只有在AdviceMode設置成AdviceMode.PROXY的情況下才會生效,加入使用ASPECTJ這AOP框架的話,這個屬性就失效了。
//                     另外,這個屬性的設定可能會影響其他需要動態代理的類。比如說將這個屬性設置成true,@Async註解的方法也會使用CGLIB生成代理類。
//                     但是總的來說,這個屬性的設置不會造成什麼負面影響,畢竟JDK動態代理和CGLIB動態代理都能實現我們的需求

//mode屬性:Spring提供的AOP功能有兩種實現方式,一種是Spring自帶的AOP功能,主要靠JDK代理和CGLIB代理實現,另外一種是通過第三方框架ASPECTJ實現。這個選項
//        就是設定Spring用哪種方式提供AOP功能。AdviceMode.PROXY表示用Spring自帶的AOP功能,AdviceMode.ASPECTJ表示使用AdviceMode提供AOP功能。
//        需要注意的是Spring自帶的AOP功能不支持本地調用的代理功能,也就是說同一個類中的方法互相調用不會“觸發”代理方法。如果想讓自調用觸發代理,可以考慮使用ASPECTJ。

//order屬性:表示當一個連接點(方法)被切多次時(也就是說有多個Advice和連接點關聯),這些連接點的執行順序。
@EnableTransactionManagement
public class TxConfig {
}

step2:在需要事務管理的方法上添加@Transactional註解

@Override
@Transactional
public int saveSysUser(SysUser user) {
    int i = sysUserMapper.insert(user);
    return i;
}

整個使用流程就這麼簡單。這篇博客就來簡單分析下Spring是怎麼實現事務管理的。

對事務管理進行AOP的過程

Spring的很多功能都是通過AOP功能實現的,事務管理也是。我們之前的文章分析過Spring基礎AOP實現的原理。這邊再簡單提下Spring實現AOP的
原理:

Spring基礎的AOP功能的開關是@EnableAspectJAutoProxy,這個註解註冊了一個Bean——AnnotationAwareAspectJAutoProxyCreator,這個Bean才是實現AOP功能的關鍵。
這個Bean實現了InstantiationAwareBeanPostProcessor接口(這個接口是BeanPostProcessor的子接口)。熟悉Spring的讀者知道,實現BeanPostProcessor接口的Bean
會在其他Bean初始化之前初始,然後在其他Bean初始化的時候,BeanPostProcessor的實現會對這些Bean進行“加工處理”。

這邊AnnotationAwareAspectJAutoProxyCreator就承擔了加工處理類的角色。這個Bean在其他Bean初始化前後會判斷這個Bean中的方法是不是有對應的Advice,如果有的話就會
通過動態代理的方式生成動態代理類將通知織入進去。

我們發現開啟事務管理的方式和開啟AOP功能的方式很像,也是通過Enable註解開啟。所以很自然就猜想事務管理是不是也是通過BeanPostProcessor的方式實現的。帶着這個猜想去看下@EnableTransactionManagement註解。


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

	boolean proxyTargetClass() default false;
	AdviceMode mode() default AdviceMode.PROXY;
	int order() default Ordered.LOWEST_PRECEDENCE;
}

看到上面的代碼,我們很自然的會去看TransactionManagementConfigurationSelector的代碼。Spring有兩種方式提供AOP功能,一種是自帶的動態代理的功能,一種是
通過ASPECTJ的方式提供。這邊主要討論Spring自帶的AOP功能。

protected String[] selectImports(AdviceMode adviceMode) {
    switch (adviceMode) {
        //用代理的方式實現事務管理的AOP功能
        case PROXY:
            return new String[] {AutoProxyRegistrar.class.getName(),
                    ProxyTransactionManagementConfiguration.class.getName()};
        case ASPECTJ:
            return new String[] {determineTransactionAspectClass()};
        default:
            return null;
    }
}

上面的代碼中,我們主要關注PROXY這個case中的方法。這個case中註冊了兩個類:AutoProxyRegistrar和ProxyTransactionManagementConfiguration。

首先我們來看AutoProxyRegistrar這個類,層層點進入,我們發現這個類最終就是註冊了InfrastructureAdvisorAutoProxyCreator這個類。仔細看InfrastructureAdvisorAutoProxyCreator
這個類實現的接口的話,你會發現這個類也是BeanPostProcesser系列的類。看到這裏,我的直覺是事務管理的AOP過程和Spring基礎的AOP功能原理可能是一樣的。

再仔細看InfrastructureAdvisorAutoProxyCreator對BeanPostProcesser系列接口的實現,你會發現都是繼承的AbstractAutoProxyCreator。看到這個驗證了我之前的想法。

下面是Spring對事務管理進行AOP的過程,你會發現和基礎的AOP功能是一套代碼。

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


protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Create proxy if we have advice.
    // 代碼1
    // 這邊是獲取Advice和Advisor的具體代碼
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        //生成代理類
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

再來看看ProxyTransactionManagementConfiguration做了些啥?點進源代碼你會發現這個類的功能很簡單,就是註冊了下面幾個事務管理相關的基礎Bean。

  • BeanFactoryTransactionAttributeSourceAdvisor;
  • TransactionAttributeSource;
  • TransactionInterceptor。

事務管理的生效過程

上面的章節中講了Spring是怎麼生成事務相關的AOP代理類的。這邊來講下Spring的事務管理是怎麼生效的——怎麼開啟事務,怎麼回滾事務,怎麼提交事務,Spring中的事務傳播
機制是怎麼生效的。

這塊的代碼主要是在TransactionAspectSupport的invokeWithinTransaction方法中(不要問我是怎麼找到這段代碼的…)。下面講下這個方法中的幾個關鍵點。


protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {

    // If the transaction attribute is null, the method is non-transactional.
    TransactionAttributeSource tas = getTransactionAttributeSource();
    //獲取TransactionAttribute,這個類主要是@Transactional註解的配置信息
    final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
    //確認事務管理器
    final TransactionManager tm = determineTransactionManager(txAttr);

    if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
        ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
            if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
                throw new TransactionUsageException(
                        "Unsupported annotated transaction on suspending function detected: " + method +
                        ". Use TransactionalOperator.transactional extensions instead.");
            }
            ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
            if (adapter == null) {
                throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
                        method.getReturnType());
            }
            return new ReactiveTransactionSupport(adapter);
        });
        return txSupport.invokeWithinTransaction(
                method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
    }

    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
        // Standard transaction demarcation with getTransaction and commit/rollback calls.
        TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

        Object retVal;
        try {
            // This is an around advice: Invoke the next interceptor in the chain.
            // This will normally result in a target object being invoked.
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // target invocation exception
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            cleanupTransactionInfo(txInfo);
        }

        if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
            // Set rollback-only in case of Vavr failure matching our rollback rules...
            TransactionStatus status = txInfo.getTransactionStatus();
            if (status != null && txAttr != null) {
                retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
            }
        }

        commitTransactionAfterReturning(txInfo);
        return retVal;
    }else {
        final ThrowableHolder throwableHolder = new ThrowableHolder();

        // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
        try {
            Object result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
                TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
                try {
                    Object retVal = invocation.proceedWithInvocation();
                    if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
                        // Set rollback-only in case of Vavr failure matching our rollback rules...
                        retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                    }
                    return retVal;
                }
                catch (Throwable ex) {
                    if (txAttr.rollbackOn(ex)) {
                        // A RuntimeException: will lead to a rollback.
                        if (ex instanceof RuntimeException) {
                            throw (RuntimeException) ex;
                        }
                        else {
                            throw new ThrowableHolderException(ex);
                        }
                    }
                    else {
                        // A normal return value: will lead to a commit.
                        throwableHolder.throwable = ex;
                        return null;
                    }
                }
                finally {
                    cleanupTransactionInfo(txInfo);
                }
            });

            // Check result state: It might indicate a Throwable to rethrow.
            if (throwableHolder.throwable != null) {
                throw throwableHolder.throwable;
            }
            return result;
        }
        catch (ThrowableHolderException ex) {
            throw ex.getCause();
        }
        catch (TransactionSystemException ex2) {
            if (throwableHolder.throwable != null) {
                logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                ex2.initApplicationException(throwableHolder.throwable);
            }
            throw ex2;
        }
        catch (Throwable ex2) {
            if (throwableHolder.throwable != null) {
                logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
            }
            throw ex2;
        }
    }
}

事務操作的主要代碼都在這個方法中,要詳細將這個方法能寫很多內容。這邊就不詳細展開了,大家感興趣的可以仔細研究下這個方法。

重要類總結

  • InfrastructureAdvisorAutoProxyCreator:事務管理AOP註冊
  • BeanFactoryTransactionAttributeSourceAdvisor:Spring事務管理基礎Bean
  • TransactionAttributeSource:Spring事務管理基礎Bean
  • TransactionInterceptor:Spring事務管理基礎Bean
  • TransactionAspectSupport的invokeWithinTransaction方法:事務處理的主要方法

相關註解

如果你仔細看過Spring的相關源代碼,會發現Spring的Enable系列的註解都是上面的“套路”,熟悉了@EnableTransactionManagement註解生效的原理,其他註解都是類似的生效規則。比如

  • @EnableAsync
  • @EnableScheduling

希望大家能做到觸類旁通。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

您可能也會喜歡…