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

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