一文詳解Spring事務注解的解析,這下你總懂了吧牍白?

前言

事務我們都知道是什么脊凰,而Spring事務就是在數據庫之上利用AOP提供聲明式事務和編程式事務幫助我們簡化開發(fā),解耦業(yè)務邏輯和系統(tǒng)邏輯茂腥。但是Spring事務原理是怎樣狸涌?事務在方法間是如何傳播的?為什么有時候事務會失效础芍?接下來幾篇文章將重點分析Spring事務源碼杈抢,讓我們徹底搞懂Spring事務的原理。

XML標簽的解析

<tx:annotation-driven transaction-manager="transactionManager"/>

配置過事務的應該都不陌生仑性,上面這個配置就是Spring開啟事務注解(@Transactional)支持的配置,而看過我之前文章的應該知道右蹦,這個帶前綴的標簽叫自定義標簽诊杆,我在之前的文章也分析過自定義標簽的解析過程,所以這里我直接找到對應的handler:

public class TxNamespaceHandler extends NamespaceHandlerSupport {
    static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
    static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
    static String getTransactionManagerName(Element element) {
        return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?
                element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
    }
    @Override
    public void init() {
        registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
    }
}

可以看到對應的注解解析器就是 AnnotationDrivenBeanDefinitionParser 類何陆,在該類中一定會有一個 parse 方法:

public BeanDefinition parse(Element element, ParserContext parserContext) {
        registerTransactionalEventListenerFactory(parserContext);
        String mode = element.getAttribute("mode");
        if ("aspectj".equals(mode)) {
            // mode="aspectj"
            registerTransactionAspect(element, parserContext);
            if (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader())) {
                registerJtaTransactionAspect(element, parserContext);
            }
        }
        else {
            // mode="proxy"
            AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
        }
        return null;
    }

首先拿到 mode 屬性的值判斷是使用AspectJ生成代理還是JDK生成代理晨汹,這里我們主要看 proxy 模式,進入 configureAutoProxyCreator 方法:

public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
            // 注冊AOP的入口類
            AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);

            String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
            if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
                Object eleSource = parserContext.extractSource(element);

                // Create the TransactionAttributeSource definition.
                // @Transactional注解的屬性封裝
                RootBeanDefinition sourceDef = new RootBeanDefinition(
                        "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
                sourceDef.setSource(eleSource);
                sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);

                // Create the TransactionInterceptor definition.
                // AOP執(zhí)行鏈
                RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
                interceptorDef.setSource(eleSource);
                interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                // 拿到transaction-manager屬性的值
                registerTransactionManager(element, interceptorDef);
                interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
                String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);

                // Create the TransactionAttributeSourceAdvisor definition.
                RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
                advisorDef.setSource(eleSource);
                advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
                advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
                if (element.hasAttribute("order")) {
                    advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
                }
                parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);

                CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
                compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
                compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
                compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
                parserContext.registerComponent(compositeDef);
            }
        }

這里的流程比較長贷盲,但邏輯很簡單淘这。首先來看注冊事務AOP入口類是哪個:

public static void registerAutoProxyCreatorIfNecessary(
            ParserContext parserContext, Element sourceElement) {

        // 將優(yōu)先級更高的AOP入口類放入到IOC容器中
        BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
                parserContext.getRegistry(), parserContext.extractSource(sourceElement));
        // 設置代理生成的方式以及是否緩存代理類到當前線程
        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
        registerComponentIfNecessary(beanDefinition, parserContext);
    }

主要看 registerAutoProxyCreatorIfNecessary 方法:

public static BeanDefinition registerAutoProxyCreatorIfNecessary(
            BeanDefinitionRegistry registry, @Nullable Object source) {

        return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
    }

    private static BeanDefinition registerOrEscalateApcAsRequired(
            Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

        // 判斷傳進來的類和ICO中當前存在的類哪個優(yōu)先級更高,將更高的放入IOC中
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }
            return null;
        }

        //把AOP入口類封裝成beanDefinition對象巩剖,要實例化
        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        //注解aop入口類的beanName名稱 AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    }

首先判斷容器中是否已經存在AOP入口類铝穷,如果不存在則直接創(chuàng)建
InfrastructureAdvisorAutoProxyCreator的BeanDefinition對象注冊到容器中,這個類也是AOP入口類AbstractAutoProxyCreator的子類佳魔,再來看看其繼承關系:

一文詳解Spring事務注解的解析曙聂,這下你總懂了吧?

你會不會疑惑鞠鲜,這么多子類宁脊,到底會使用哪一個呢?回到剛剛的代碼中贤姆,可以看到如果已經存在一個入口類了榆苞,就會通過 findPriorityForClass 獲取兩個類的優(yōu)先級,最終就會使用優(yōu)先級更大的那個霞捡,那么它們的優(yōu)先級順序是怎樣的呢坐漏?

private static final List<Class<?>> APC_PRIORITY_LIST = new ArrayList<>(3);

    static {
        // Set up the escalation list...
        APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
        APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
        APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
    }

    private static int findPriorityForClass(@Nullable String className) {
        // 索引即是優(yōu)先級,越大優(yōu)先級越高,IOC中只會存在一個事務AOP入口類
        for (int i = 0; i < APC_PRIORITY_LIST.size(); i++) {
            Class<?> clazz = APC_PRIORITY_LIST.get(i);
            if (clazz.getName().equals(className)) {
                return i;
            }
        }
        throw new IllegalArgumentException(
                "Class name [" + className + "] is not a known auto-proxy creator class");
    }

可以看到仙畦,
InfrastructureAdvisorAutoProxyCreator是優(yōu)先級最低的输涕,基本上不會起作用;AspectJAwareAdvisorAutoProxyCreator是當我們配置了<aop:config>標簽時會注冊慨畸,也就是xml配置的AOP的入口類莱坎;而AnnotationAwareAspectJAutoProxyCreator是當我們配置了<aop:aspectj-autoproxy>或使用@EnableAspectJAutoProxy注解時注冊,因此大部分情況下都是使用的AnnotationAwareAspectJAutoProxyCreator寸士。

注冊完AOP的入口類后檐什,回到configureAutoProxyCreator方法:

RootBeanDefinition sourceDef = new RootBeanDefinition(
        "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
sourceDef.setSource(eleSource);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String sourceName =parserContext.getReaderContext().registerWithGeneratedName(sourceDef);

AnnotationTransactionAttributeSource類的作用就是封裝事務注解@Transactional的屬性,這里需要記住其繼承體系以及熟悉該類和其父類的屬性和方法弱卡,對后面分析事物切面執(zhí)行原理有幫助:

一文詳解Spring事務注解的解析乃正,這下你總懂了吧?

緊接著就是創(chuàng)建了TransactionInterceptor對象婶博,專門的事務攔截器瓮具,并且該類是MethodInterceptor的子類,看到這個應該不陌生了凡人,我們知道AOP調用鏈在執(zhí)行過程中主要就是調用該類的invoke的方法名党,因此它是事務切面執(zhí)行的入口。既然有了Interceptor挠轴,那么必不可少的還應該有Advisor传睹,而Advisor又是由Advice和Poincut組成的,這樣才能構成一個完整的切面岸晦,所以該方法后面就是創(chuàng)建這兩個對象欧啤。以上就是xml配置AOP注解支持的原理,很簡單启上,下面再來看看零配置又是如何實現的邢隧。

AOP零配置原理

使用過SpringBoot的都知道,如果需要開啟事務注解的支持碧绞,只需要一個注解就能搞定:@
EnableTransactionManagement府框,不用再配置xml文件,這個又是怎么做到的呢讥邻?不多說迫靖,我們直接來看其源碼:

@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default Ordered.LOWEST_PRECEDENCE;
}

在該注解下使用@Import導入了一個類
TransactionManagementConfigurationSelector,首先該注解的作用就是導入一個類的實例到IOC容器中兴使,你可能會說不是在類上加@Component注解就行了么系宜,但是有些類它并不在你掃描的路徑下,而該注解依然可以將其導入進來发魄,所以我么主要看TransactionManagementConfigurationSelector類中做了些啥:

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return new String[] {AutoProxyRegistrar.class.getName(),
                        ProxyTransactionManagementConfiguration.class.getName()};
            case ASPECTJ:
                return new String[] {determineTransactionAspectClass()};
            default:
                return null;
        }
    }

    private String determineTransactionAspectClass() {
        return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
                TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
                TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
    }

}

可以看到在selectImports方法中返回了AutoProxyRegistrar和
ProxyTransactionManagementConfiguration類盹牧,返回后會被封裝為BeanDefinition對象俩垃,那這個方法是在哪里調用的呢?這個在之前的文章中也分析過汰寓,ConfigurationClassPostProcessor類中會調用ConfigurationClassParser類的parse方法解析@Configuration口柳、@Import、@ImportSource等注解有滑,具體過程這里就不再贅述了跃闹。我們繼續(xù)來分別看看AutoProxyRegistrar和ProxyTransactionManagementConfiguration類:

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    private final Log logger = LogFactory.getLog(getClass());

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean candidateFound = false;
        Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
        for (String annoType : annoTypes) {
            AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
            if (candidate == null) {
                continue;
            }
            Object mode = candidate.get("mode");
            Object proxyTargetClass = candidate.get("proxyTargetClass");
            if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
                    Boolean.class == proxyTargetClass.getClass()) {
                candidateFound = true;
                if (mode == AdviceMode.PROXY) {
                    //注冊事務AOP的入口類InfrastructureAdvisorAutoProxyCreator,實際上這個AOP入口類起不了作用
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    if ((Boolean) proxyTargetClass) {
                        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                        return;
                    }
                }
            }
        }
    }
}

public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

    /*
    * 明顯是創(chuàng)建事務切面實例
    * BeanFactoryTransactionAttributeSourceAdvisor
    *
    * */
    @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
        BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        advisor.setTransactionAttributeSource(transactionAttributeSource());
        //設置通知類
        advisor.setAdvice(transactionInterceptor());
        if (this.enableTx != null) {
            advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
        }
        return advisor;
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionAttributeSource transactionAttributeSource() {
        return new AnnotationTransactionAttributeSource();
    }

    /*
    * 創(chuàng)建事務advice
    * TransactionInterceptor
    * */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionInterceptor transactionInterceptor() {
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionAttributeSource(transactionAttributeSource());
        //事務管理器要跟數據源掛鉤,所以需要自己定義
        if (this.txManager != null) {
            interceptor.setTransactionManager(this.txManager);
        }
        return interceptor;
    }

}

看到這就很清楚了毛好,前者是注冊AOP的入口類(這里注冊的入口類依然是
InfrastructureAdvisorAutoProxyCreator)望艺,后者則是創(chuàng)建事務AOP的組件的實例到IOC中,到這里相信不僅僅是對于事務的零配置肌访,而是整個SpringBoot的零配置實現原理都心中有數了找默。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吼驶,隨后出現的幾起案子惩激,更是在濱河造成了極大的恐慌,老刑警劉巖蟹演,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咧欣,死亡現場離奇詭異,居然都是意外死亡轨帜,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門衩椒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚌父,“玉大人,你說我怎么就攤上這事毛萌」冻冢” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵阁将,是天一觀的道長膏秫。 經常有香客問我,道長做盅,這世上最難降的妖魔是什么缤削? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮吹榴,結果婚禮上亭敢,老公的妹妹穿的比我還像新娘。我一直安慰自己图筹,他們只是感情好帅刀,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布让腹。 她就那樣靜靜地躺著,像睡著了一般扣溺。 火紅的嫁衣襯著肌膚如雪骇窍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天锥余,我揣著相機與錄音腹纳,去河邊找鬼。 笑死哈恰,一個胖子當著我的面吹牛只估,可吹牛的內容都是我干的。 我是一名探鬼主播着绷,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蛔钙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了荠医?” 一聲冷哼從身側響起吁脱,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎彬向,沒想到半個月后兼贡,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡娃胆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年遍希,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片里烦。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡凿蒜,死狀恐怖,靈堂內的尸體忽然破棺而出胁黑,到底是詐尸還是另有隱情废封,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布丧蘸,位于F島的核電站漂洋,受9級特大地震影響,放射性物質發(fā)生泄漏力喷。R本人自食惡果不足惜刽漂,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冗懦。 院中可真熱鬧爽冕,春花似錦、人聲如沸披蕉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至眯娱,卻和暖如春礁苗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背徙缴。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工试伙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人于样。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓疏叨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親穿剖。 傳聞我的和親對象是個殘疾皇子蚤蔓,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容