面試官:談?wù)凷pring事務(wù)實(shí)現(xiàn)原理
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來,我們一起精進(jìn)!你不來,我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!
編輯:業(yè)余草
cnblogs.com/insaneXs/p/13638034.html
推薦:https://www.xttblog.com/?p=5281
前言
對(duì)于一個(gè)應(yīng)用而言,事務(wù)的使用基本是不可避免的。雖然 Spring 給我們提供了開箱即用的事務(wù)功能 @Transactional,但是,自帶的事務(wù)功能卻也存在控制粒度不夠的缺點(diǎn)。更糟糕的是,@Transactional在某些情況下就失效了。可能一些讀者 baidu/google 一下解決辦法后,失效的問題確實(shí)解決了。但是由于不了解底層的原理,這樣的問題可能在今后的工作中往復(fù)出現(xiàn)。

本文就為大家揭開@Transactional下的秘密。
原生的事務(wù)管理
在沒有 Spring 存在的時(shí)候,事務(wù)就已經(jīng)誕生了。其實(shí)框架依賴的還是底層提供的能力,只不過它對(duì)這一過程的抽象和復(fù)用。
這里我們用底層的 API 來了解下事務(wù)管理的過程( JDBC 為例):
//?獲取mysql數(shù)據(jù)庫(kù)連接
Connection?conn?=?DriverManager.getConnection("xxxx");
conn.setAutoCommit(false);
statement?=?conn.createStatement();
//?執(zhí)行sql,返回結(jié)果集
resultSet?=?statement.executeQuery("xxxx");
conn.commit();?//提交
//回滾
//conn.rollback();
上面是一個(gè)原生操作事務(wù)的一個(gè)例子,這些過程也是 Spring 事務(wù)逃不開的,只不過在為了編程的效率讓這一過程自動(dòng)化或是透明化的你無(wú)法感知罷了。
而我們之后做的就是逐步還原這一自動(dòng)化的過程。
Spring提供的事務(wù)API
Spring 提供了很多關(guān)于事務(wù)的 API。但是最為基本的就是PlatformTransactionManager、TransactionDefintion和TransactionStatus。
事務(wù)管理器 PlatformTransactionManager
PlatformTransactionManager是事務(wù)管理器的頂層接口。事務(wù)的管理是受限于具體的數(shù)據(jù)源的(例如,JDBC 對(duì)應(yīng)的事務(wù)管理器就是DatasourceTransactionManager),因此PlatformTransactionManager只規(guī)定了事務(wù)的基本操作:創(chuàng)建事務(wù),提交事物和回滾事務(wù)。
public?interface?PlatformTransactionManager?extends?TransactionManager?{
????/**
?????*?打開事務(wù)
?????*/
????TransactionStatus?getTransaction(@Nullable?TransactionDefinition?definition)
????????????throws?TransactionException;
????/**
?????*?提交事務(wù)
?????*/
????void?commit(TransactionStatus?status)?throws?TransactionException;
????/**
?????*?回滾事務(wù)
?????*/
????void?rollback(TransactionStatus?status)?throws?TransactionException;
}
同時(shí)為了簡(jiǎn)化事務(wù)管理器的實(shí)現(xiàn),Spring 提供了一個(gè)抽象類AbstractPlatformTransactionManager,規(guī)定了事務(wù)管理器的基本框架,僅將依賴于具體平臺(tái)的特性作為抽象方法留給子類實(shí)現(xiàn)。
事務(wù)狀態(tài) TransactionStatus
事務(wù)狀態(tài)是我對(duì)TransactionStatus這個(gè)類的直譯。其實(shí)我覺得這個(gè)類可以直接當(dāng)作事務(wù)的超集來看(包含了事務(wù)對(duì)象,并且存儲(chǔ)了事務(wù)的狀態(tài))。PlatformTransactionManager.getTransaction()時(shí)創(chuàng)建的也正是這個(gè)對(duì)象。
這個(gè)對(duì)象的方法都和事務(wù)狀態(tài)相關(guān):
public?interface?TransactionStatus?extends?TransactionExecution,?SavepointManager,?Flushable?{
????/**
?????*?是否有Savepoint?Savepoint是當(dāng)事務(wù)回滾時(shí)需要恢復(fù)的狀態(tài)
?????*/
????boolean?hasSavepoint();
????/**
?????*?flush()操作和底層數(shù)據(jù)源有關(guān),并非強(qiáng)制所有數(shù)據(jù)源都要支持
?????*/
????@Override
????void?flush();
}
此外,TransactionStatus還從父接口中繼承了其他方法,都?xì)w總在下方:
/**
*?是否是新事務(wù)(或是其他事務(wù)的一部分)
*/
boolean?isNewTransaction();
/**
*?設(shè)置rollback-only?表示之后需要回滾
*/
void?setRollbackOnly();
/**
*?是否rollback-only
*/
boolean?isRollbackOnly();
/**
*?判斷該事務(wù)已經(jīng)完成
*/
boolean?isCompleted();
/**
*?創(chuàng)建一個(gè)Savepoint
*/
Object?createSavepoint()?throws?TransactionException;
/**
*?回滾到指定Savepoint
*/
void?rollbackToSavepoint(Object?savepoint)?throws?TransactionException;
/**
*?釋放Savepoint?當(dāng)事務(wù)完成后,事務(wù)管理器基本上自動(dòng)釋放該事務(wù)所有的savepoint
*/
void?releaseSavepoint(Object?savepoint)?throws?TransactionException;
事務(wù)屬性的定義 TransactionDefinition
TransactionDefinition表示一個(gè)事務(wù)的定義,將根據(jù)它規(guī)定的特性去開啟事務(wù)。
事務(wù)的傳播等級(jí)和隔離級(jí)別的常量同樣定義在這個(gè)接口中。
/**
?*?返回事務(wù)的傳播級(jí)別
?*/
default?int?getPropagationBehavior()?{
?????return?PROPAGATION_REQUIRED;
}
/**
?*?返回事務(wù)的隔離級(jí)別
?*/
default?int?getIsolationLevel()?{
?????return?ISOLATION_DEFAULT;
}
/**
?*?事務(wù)超時(shí)時(shí)間
?*/
default?int?getTimeout()?{
?????return?TIMEOUT_DEFAULT;
}
/**
?*?是否為只讀事務(wù)(只讀事務(wù)在處理上能有一些優(yōu)化)
?*/
default?boolean?isReadOnly()?{
?????return?false;
}
/**
?*?返回事務(wù)的名稱
?*/
@Nullable
default?String?getName()?{
?????return?null;
}
/**
?*?默認(rèn)的事務(wù)配置
?*/
static?TransactionDefinition?withDefaults()?{
?????return?StaticTransactionDefinition.INSTANCE;
}
編程式使用Spring事務(wù)
有了上述這些 API,就已經(jīng)可以通過編程的方式實(shí)現(xiàn) Spring 的事務(wù)控制了。
但是 Spring 官方建議不要直接使用PlatformTransactionManager這一偏低層的 API 來編程,而是使用TransactionTemplate和TransactionCallback這兩個(gè)偏向用戶層的接口。
示例代碼如下:
//設(shè)置事務(wù)的各種屬性;可以猜測(cè)TransactionTemplate應(yīng)該是實(shí)現(xiàn)了TransactionDefinition
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transactionTemplate.setTimeout(30000);
//執(zhí)行事務(wù)?將業(yè)務(wù)邏輯封裝在TransactionCallback中
transactionTemplate.execute(new?TransactionCallback以上就是 Spring 事務(wù)最基本的原理。但是為什么這些過程對(duì)我們似乎都不可見呢?那是因?yàn)檫@些過程都「通過AOP的方式被織入」了我們的業(yè)務(wù)邏輯中。
所以,像要深入了解 Spring 事務(wù)原理,還需要了解 AOP 的原理。
AOP的原理
AOP 的實(shí)現(xiàn)機(jī)制有兩種:Proxy-based 和 Weaving-based。
前者是依賴動(dòng)態(tài)代理的方式達(dá)到對(duì)代理類增強(qiáng)的目的。后者應(yīng)該是通過字節(jié)碼增強(qiáng)的方式達(dá)到增強(qiáng)的目的。
在 Spring 中,一般默認(rèn)使用前者。之后也僅是針對(duì)前者進(jìn)行分析。
而 Spring 聲明 AOP 的方式也有兩種,一種是通過聲明 Aspect,另一種是通過聲明 Advisor。
無(wú)論是哪種方式,都需要表達(dá)清楚你要進(jìn)行增強(qiáng)的邏輯 (what)和你要增強(qiáng)的地方(where)。即,需要告訴 Spring 你要增強(qiáng)什么邏輯,并且對(duì)哪些 Bean/哪些方法增強(qiáng)。
這里的 what 和 where 換成 AOP 中的概念分別就是對(duì)應(yīng)Advice和Pointcut。
因?yàn)槭聞?wù)是通過 Advisor 聲明 AOP 的,因此本文也只針對(duì) Advisor 的實(shí)現(xiàn)展開分析。
動(dòng)態(tài)代理
既然是動(dòng)態(tài)代理,那么必然存在被代理類(Target),代理類(Proxy),以及類被代理的過程(因?yàn)閷?duì)用戶而言,并不知道類被代理了)。
被代理的類
被代理類是最容易知道的,就是那些被 Advisor 的 Pointcut 匹配(classFliter 匹配或是 methodMatches)到的類。
代理的類
而代理類是在運(yùn)行時(shí)直接創(chuàng)建的。通常有兩種方式:
JDK 的動(dòng)態(tài)代理 CGLIB 的動(dòng)態(tài)代理
二者的區(qū)別是 JDK 動(dòng)態(tài)代理是通過實(shí)現(xiàn)接口的方式(代理的對(duì)象為接口),因此只能代理接口中的方法。
而 CGLIB 動(dòng)態(tài)代理是通過繼承的方式,因此可以對(duì)對(duì)象中的方法進(jìn)行代理,但是由于是繼承關(guān)系,無(wú)法代理 final 的類和方法(無(wú)法繼承),或是 private 的方法(對(duì)子類不可見)。
創(chuàng)建代理及取代目標(biāo)類的過程
創(chuàng)建代理及取代目標(biāo)類主要是應(yīng)用了 Spring 容器在獲取 Bean 時(shí)留下的一個(gè)拓展點(diǎn)。
Spring 在getBean的時(shí)候,如果 Bean 還不存在會(huì)分三步去創(chuàng)建 Bean:
實(shí)例化 填充屬性 初始化
實(shí)例化通常是通過反射創(chuàng)建 Bean 對(duì)象的實(shí)例,此時(shí)得到的 Bean 還只是一個(gè)空白對(duì)象。
填充屬性主要是為這個(gè) Bean 注入其他的 Bean,實(shí)現(xiàn)自動(dòng)裝配。
而初始化則是讓用戶可以控制 Bean 的創(chuàng)建過程。
為 Bean 創(chuàng)建代理,并取代原有的 Bean 就是發(fā)生在初始化這一步,更具體的是在BeanPostProcessor.postProcessorAfterInitialization()中。
?動(dòng)態(tài)代理也有可能在實(shí)例化之前直接創(chuàng)建代理,這種情況發(fā)生在
?InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()中,此時(shí)的實(shí)例化過程不再是我們上文介紹的通過簡(jiǎn)單反射創(chuàng)建對(duì)象。
在眾多的BeanPostProcessor中有一類后置處理器就是專門用于創(chuàng)建代理的。例如,我們要介紹的AbstractAdvisorAutoProxyCreator。
看一下AbstractAutoProxyCreator創(chuàng)建代理的流程:
先確認(rèn)是否已經(jīng)創(chuàng)建過代理對(duì)象( earlyProxyReferences,避免對(duì)代理對(duì)象在進(jìn)行代理)如果沒有,則考慮是否需要進(jìn)行代理(通過 wrapIfNecessary)如果是特殊的Bean 或者之前判斷過不用創(chuàng)建代理的Bean則不創(chuàng)建代理 否則看是否有匹配的Advise(匹配方式就是上文介紹的通過PointCut或者IntroducationAdvisor可以直接匹配類) 如果找到了Advisor,說明需要?jiǎng)?chuàng)建代理,進(jìn)入 createProxy首先會(huì)創(chuàng)建 ProxyFactory,這個(gè)工廠是用來創(chuàng)建AopProxy的,而AopProxy才是用來創(chuàng)建代理對(duì)象的。因?yàn)榈讓哟矸绞接袃煞N(JDK動(dòng)態(tài)代理和CGLIB,對(duì)應(yīng)到AopProxy的實(shí)現(xiàn)就是JdkDynamicAopProxy和ObjenesisCglibAopProxy),所以這里使用了一個(gè)簡(jiǎn)單工廠的設(shè)計(jì)。ProxyFactory會(huì)設(shè)置此次代理的屬性,然后根據(jù)這些屬性選擇合適的代理方式,創(chuàng)建代理對(duì)象。創(chuàng)建的對(duì)象會(huì)替換掉被代理對(duì)象(Target),被保存在 BeanFactory.singletonObjects,因此當(dāng)有其他Bean希望注入Target時(shí),其實(shí)已經(jīng)被注入了Proxy。
以上就是 Spring 實(shí)現(xiàn)動(dòng)態(tài)代理的過程。
Spring注解式事務(wù)
我們從編程式事務(wù)了解了 Spring 事務(wù) API 的基本使用方式,又了解了 Spring Advisor 的原理。現(xiàn)在,我們?cè)诨氐?Spring 注解式事務(wù)中,驗(yàn)證下注解式事務(wù)是否就是通過以上這些方式隱藏了具體的事務(wù)控制邏輯。
從@EnableTransactionManagement說起
@EnableTransactionManagement是開啟注解式事務(wù)的事務(wù)。如果注解式事務(wù)真的有玄機(jī),那么@EnableTransactionManagement就是我們揭開秘密的突破口。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public?@interface?EnableTransactionManagement?{
????/**
?????*?用來表示默認(rèn)使用JDK?Dynamic?Proxy還是CGLIB?Proxy
?????*/
????boolean?proxyTargetClass()?default?false;
????/**
?????*?表示以Proxy-based方式實(shí)現(xiàn)AOP還是以Weaving-based方式實(shí)現(xiàn)AOP
?????*/
????AdviceMode?mode()?default?AdviceMode.PROXY;
????/**
?????*?順序
?????*/
????int?order()?default?Ordered.LOWEST_PRECEDENCE;
}
@EnableTransactionManagement注解看起來并沒有特別之處,都是一些屬性的配置。但它卻通過@Import引入了另一個(gè)配置TransactionManagentConfigurationSelector。
TransactionManangementConfigurationSelector
在 Spring 中,Selector通常都是用來選擇一些 Bean,向容器注冊(cè)BeanDefinition的(嚴(yán)格意義上 Selector 僅時(shí)選擇過程,注冊(cè)的具體過程是在ConfigurationClasspathPostProcessor解析時(shí),調(diào)用ConfigurationClassParser觸發(fā))。
主要的邏輯就是根據(jù)代理模式,注冊(cè)不同的 BeanDefinition。
對(duì) Proxy 的模式而言,注入的有兩個(gè):
AutoProxyRegistrar ProxyTransactionManagementConfiguration
AutoProxyRegistrar
Registrar 同樣也是用來向容器注冊(cè) Bean 的,在 Proxy 的模式下,它會(huì)調(diào)用AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);向容器中注冊(cè)InfrastructureAdvisorAutoProxyCreator。而這個(gè)類就是我們上文提到的AbstractAdvisorAutoProxyCreator的子類。
從而,我們完成了我們的第一個(gè)條件——AOP代理。
ProxyTransactionManagementConfiguration
ProxyTransactionManagementConfiguration是一個(gè)配置類,如果算上其繼承的父類,一共是聲明了四個(gè)類:
TransactionalEventListenerFactory BeanFactoryTransactionAttributeSourceAdvisor TransactionAttributeSource TransactionInterceptor
后三個(gè)類相對(duì)比較重要,我們一一分析。
BeanFactoryTransactionAttributeSourceAdvisor
從名字看就知道這是一個(gè) Advisor,那么它身上應(yīng)該有 Pointcut 和 Advise。
其中的 Pointcut 是TransactionAttributeSourcePointcut,主要是一些 filter 和 matches 之類的方法,用來匹配被代理類。
而 Adivise 就是我們之后要介紹的TransactionInterceptor。
TransactionAttributeSource
TransactionAttributeSource只是一個(gè)接口,擴(kuò)展了TransactionDefinition,增加了isCandidateClass()的方法(可以用來幫助 Pointcut 匹配)。
這里使用的具體實(shí)現(xiàn)是AnnotationTransactionAttributeSource。因?yàn)樽⒔馐绞聞?wù)候選類(即要被代理的類)是通過@Transactional注解標(biāo)識(shí)的,并且所有的事務(wù)屬性也都來自@Transactional注解。
TransactionInterceptor
剛才我們說了,TransactionInterceptor就是我們找的Advise。
這個(gè)類稍微復(fù)雜一點(diǎn),首先根據(jù)事務(wù)處理相關(guān)的邏輯都放在了其父類TransactionAspectSupport中。此外,為了適配動(dòng)態(tài)代理的反射調(diào)用(兩種代理方式),實(shí)現(xiàn)了MethodInterceptor接口。
也就是說,反射發(fā)起的入口是MethodInterceptor.invoke(),而反射邏輯在TransactionAspectSupport.invokeWithinTransaction()中。
我們可以簡(jiǎn)單看invokeWithTransaction()方法中的部分代碼:
@Nullable
protected?Object?invokeWithinTransaction(Method?method,?@Nullable?Class>?targetClass,
????????final?InvocationCallback?invocation)?throws?Throwable?{
????
????TransactionAttributeSource?tas?=?getTransactionAttributeSource();
????final?TransactionAttribute?txAttr?=?(tas?!=?null???tas.getTransactionAttribute(method,?targetClass)?:?null);
????final?TransactionManager?tm?=?determineTransactionManager(txAttr);
????//省略部分代碼
????
????//獲取事物管理器
????PlatformTransactionManager?ptm?=?asPlatformTransactionManager(tm);
????final?String?joinpointIdentification?=?methodIdentification(method,?targetClass,?txAttr);
????if?(txAttr?==?null?||?!(ptm?instanceof?CallbackPreferringPlatformTransactionManager))?{
????????//?打開事務(wù)(內(nèi)部就是getTransactionStatus的過程)
????????TransactionInfo?txInfo?=?createTransactionIfNecessary(ptm,?txAttr,?joinpointIdentification);
????????Object?retVal;
????????try?{
????????????//?執(zhí)行業(yè)務(wù)邏輯?invocation.proceedWithInvocation();
????????}
????????catch?(Throwable?ex)?{
????????????//?異常回滾
????????????completeTransactionAfterThrowing(txInfo,?ex);
????????????throw?ex;
????????}
????????finally?{
????????????cleanupTransactionInfo(txInfo);
????????}
????????//省略部分代碼
????????
????????//提交事物
????????commitTransactionAfterReturning(txInfo);
????????return?retVal;
????}
}
雖然代碼比我們之前的復(fù)雜,但是其主體結(jié)構(gòu)依然是我們編程式事務(wù)的常見那幾步。
行文至此,隱藏在 Spring 自動(dòng)事務(wù)下的邏輯都分析的差不多了。未避免枯燥,本文并沒有對(duì)代碼一行行的分析,而是希望能夠幫助讀者把握大概的原理。
事務(wù)失效的常見情況及其背后的原因
數(shù)據(jù)庫(kù)存儲(chǔ)引擎不支持
常見的像 mySQL 的 myISAM 存儲(chǔ)引擎就不支持事務(wù)功能。
這很好理解,說到底事務(wù)是數(shù)據(jù)庫(kù)的功能,如果數(shù)據(jù)庫(kù)本身就沒有這個(gè)功能,那上層再怎么五花八門也是沒用的。
未指定RollbackOn,且拋出的異常并非RuntimeException
這個(gè)背后的原因我們可以從DefualtTransactionAttribute中來找。
//可見默認(rèn)觸發(fā)回滾的異常是RuntimeException和Error
@Override
public?boolean?rollbackOn(Throwable?ex)?{
????return?(ex?instanceof?RuntimeException?||?ex?instanceof?Error);
}
因此阿里巴巴代碼規(guī)范倡議是顯示指定 rollbackOn 為 Exception
同一個(gè)類中調(diào)用事務(wù)方法
這是在 Proxy 模式下才會(huì)失效的。
根據(jù)上文我們了解了 Spring 事務(wù)是機(jī)遇動(dòng)態(tài)代理的,而當(dāng)在類當(dāng)中調(diào)用事務(wù)的方法時(shí),動(dòng)態(tài)代理是無(wú)法生效的,因?yàn)榇藭r(shí)你拿到的 this 指向的已經(jīng)是被代理類(Target),而非代理類(Proxy)。
非公開方法上的事務(wù)
如果你將@Transactional注解應(yīng)用在一個(gè) non-public 的方法上(即便是 protected 和 defualt 的方法),你會(huì)發(fā)現(xiàn)事務(wù)同樣不生效(也是在 Proxy 模式下)。
有讀者可能會(huì)疑問,GCLIB 的局限應(yīng)該是在 private 或是 final 的方法上,private 方法代理失效還能理解,為什么 protected 和 defualt 方法也不行呢?
其實(shí),non-public 方法失效的真正原因不是動(dòng)態(tài)代理的限制,而是 Spring 有意為之。
之前我們介紹了TransactionAttributeSource會(huì)幫助 Pointcut 匹配類和方法,而在AnnotationTransactionAttributeSource中,有一個(gè)屬性final boolean publicMethodsOnly表示是否只允許公有方法。這個(gè)屬性在默認(rèn)的構(gòu)造函數(shù)中被設(shè)置了 true。因此代理只會(huì)對(duì) public 方法生效。
網(wǎng)上找了下 Spring 這么設(shè)計(jì)的目的,有說業(yè)務(wù)類就是應(yīng)該基于接口方法調(diào)用的,因此總為 public。也有說這是為了讓 CGLIB 和 JDK dynamic Proxy 保持一致。
Emm... 我覺得 Duck 不必。
不過 Spring 也沒有把著屬性限制死,如果你真想在 non-public 的方法上使用自動(dòng)事務(wù),使點(diǎn)手段修改這個(gè)變量即可(例如搞個(gè)高優(yōu)先級(jí)的BeanPostProcessor,在通過反射修改這個(gè)變量)。但是盡量還是按照規(guī)范來吧。
