新聞中心
你好呀,我是歪歪。

在將樂(lè)等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供網(wǎng)站設(shè)計(jì)、做網(wǎng)站 網(wǎng)站設(shè)計(jì)制作按需開(kāi)發(fā)網(wǎng)站,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),品牌網(wǎng)站建設(shè),全網(wǎng)營(yíng)銷推廣,外貿(mào)網(wǎng)站制作,將樂(lè)網(wǎng)站建設(shè)費(fèi)用合理。
是的,正如標(biāo)題描述的這樣,我試圖通過(guò)這篇文章,教會(huì)你如何閱讀源碼。
事情大概是這樣的,前段時(shí)間,我收到了一個(gè)讀者發(fā)來(lái)的類似于這樣的示例代碼:
他說(shuō)他知道這三個(gè)案例的回滾情況是這樣的:
- insertTestNoRollbackFor:不會(huì)回滾
- insertTestRollback:會(huì)回滾
- insertTest:會(huì)回滾
他說(shuō)在沒(méi)有執(zhí)行代碼之前,他也知道前兩個(gè)為什么一個(gè)不會(huì)回滾,一個(gè)會(huì)回滾。因?yàn)閽伋龅漠惓:?@Transactional 里面的注解呼應(yīng)上了。
但是第三個(gè)到底會(huì)不會(huì)回滾,沒(méi)有執(zhí)行之前,他不知道為什么會(huì)回滾。執(zhí)行之后,回滾了,他也不知道為什么回滾了。
我告訴他:源碼之下無(wú)秘密。
讓他去看看這部分源碼,理解它的原理,不然這個(gè)地方拋出一個(gè)其他的異常,又不知道會(huì)不會(huì)回滾了。
但是他說(shuō)他完全不會(huì)看源碼,找不到下手的角度。
所以,就這個(gè)問(wèn)題,我打算寫這樣的一篇文章,試圖教會(huì)你一種閱讀源碼的方式。讓你找到一個(gè)好的切入點(diǎn),或者說(shuō)突破口。
但是需要事先說(shuō)明的是,閱讀源碼的方式非常的多,這篇文章只是站在我個(gè)人的角度介紹閱讀源碼的眾多方式中的一種,滄海一粟,就像是一片樹(shù)林里面的一棵樹(shù)的樹(shù)干上的一葉葉片的葉脈中的一個(gè)小分叉而已。
對(duì)于啃源碼這件事兒,沒(méi)有一個(gè)所謂的“一招吃遍天下”的秘訣,如果你非要讓我給出一個(gè)秘訣的話,那么就只有一句話:
啃源碼的過(guò)程,一定是非常枯燥的,特別是啃自己接觸不多的框架源碼的時(shí)候,千頭萬(wàn)緒,也得下手去捋,所以一定要耐得住寂寞才行。
然后,如果你非得讓我再補(bǔ)充一句的話,那么就是:
調(diào)試源碼,一定要親!自!動(dòng)!手!只是去看相關(guān)的文章,而沒(méi)有自己一步步的去調(diào)試源碼,那你相當(dāng)于看了個(gè)寂寞。
親自動(dòng)手的第一步就是搞個(gè) Demo 出來(lái)。用“黑話”來(lái)說(shuō),這個(gè) Demo 就是你的抓手,有了抓手你才能打出一套理論結(jié)合實(shí)際的組合拳。抓手多了,就能沉淀出可復(fù)用的方法論,最終為自己賦能。
搭建 Demo
所以,第一步肯定是先把 Demo 給搭建起來(lái),項(xiàng)目結(jié)構(gòu)非常的簡(jiǎn)單,標(biāo)準(zhǔn)的三層結(jié)構(gòu):
主要是一個(gè) Controller,一個(gè) Service,然后搞個(gè)本地?cái)?shù)據(jù)庫(kù)給接上,就完全夠夠的了:
Student 對(duì)象是從表里面映射過(guò)來(lái)的,隨便弄了兩個(gè)字段,主要是演示用:
就這么一點(diǎn)代碼,給你十分鐘,你是不是就能搭建好了?中間甚至還能摸幾分鐘魚(yú)。
要是只有這么一點(diǎn)東西的、極其簡(jiǎn)單的 Demo 你都不想自己親自動(dòng)手搭一下,然后自己去調(diào)試的話,僅僅是通過(guò)閱讀文章來(lái)肉眼調(diào)試,那么我只能說(shuō):
在正式開(kāi)始調(diào)試代碼之前,我們還得明確一下調(diào)試的目的:想要知道 Spring 的 @Transactional 注解對(duì)于異常是否應(yīng)該回滾的判斷邏輯具體是怎么樣的。
帶著問(wèn)題去調(diào)試源碼,是最容易有收獲的,而且你的問(wèn)題越具體,收獲越快。你的問(wèn)題越籠統(tǒng),就越容易在源碼里面迷失。
方法論之關(guān)注調(diào)用棧
自己 Debug 的過(guò)程就是不斷的打斷點(diǎn)的過(guò)程。
我再說(shuō)一次:自己 Debug 的過(guò)程就是不斷的打斷點(diǎn)的過(guò)程。
打斷點(diǎn)大家都會(huì)打,斷點(diǎn)打在哪些地方,這個(gè)玩意就很講究了。
在我們的這個(gè) Demo 下,第一個(gè)斷點(diǎn)的位置非常好判斷,就打在事務(wù)方法的入口處:
一般來(lái)說(shuō),大家調(diào)試業(yè)務(wù)代碼的時(shí)候,都是順著斷點(diǎn)往下調(diào)試。但是當(dāng)你去閱讀框架代碼的時(shí)候,你得往回看。
什么是“往回看”呢?
當(dāng)你的程序在斷點(diǎn)處停下的時(shí)候,你會(huì)發(fā)現(xiàn) IDEA 里面有這樣的一個(gè)部分:
這個(gè)調(diào)用棧是你在調(diào)試的過(guò)程中,一個(gè)非常非常非常重要的部分。
它表示的是以當(dāng)前斷點(diǎn)位置為終點(diǎn)的程序調(diào)用鏈路。
為了讓你徹底的明白這句話,我給你看一張圖:
我在 test6 方法中打上斷點(diǎn),調(diào)用棧里面就是以 test6 方法為終點(diǎn)到 main 方法為起點(diǎn)的程序調(diào)用鏈接。
當(dāng)你去點(diǎn)擊這個(gè)調(diào)用棧的時(shí)候,你會(huì)發(fā)現(xiàn)程序也會(huì)跟著動(dòng):
“跟著動(dòng)”的這個(gè)動(dòng)作,你可以理解為你站著斷點(diǎn)處“往回看”的過(guò)程。
當(dāng)你理解了調(diào)用棧是干啥的了之后,我們?cè)倬唧w看看在當(dāng)前的 Demo 下,這個(gè)調(diào)用棧里面都有寫啥:
標(biāo)號(hào)為 ① 的地方,是 TestController 方法,也就是程序的入口。
標(biāo)號(hào)為 ② 的地方,從包名稱可以看出是 String AOP 相關(guān)的方法。
標(biāo)號(hào)為 ③ 的地方,就可以看到是事務(wù)相關(guān)的邏輯了。
標(biāo)號(hào)為 ④ 的地方,是當(dāng)前斷點(diǎn)處。
好,到這里,我想讓你簡(jiǎn)單的回顧一下你來(lái)調(diào)試代碼的目的是什么?
是不是想要知道 Spring 的 @Transactional 注解對(duì)于異常是否應(yīng)該回滾的判斷邏輯具體是怎么樣的。
那么,我們是不是應(yīng)該主要把關(guān)注的重點(diǎn)放在標(biāo)號(hào)為 ③ 的地方?
也就是對(duì)應(yīng)到這一行:
這個(gè)地方我一定要特別的強(qiáng)調(diào)一下:要保持目標(biāo)清晰,很多人在源碼里面迷失的原因就是不知不覺(jué)間被源碼牽著走遠(yuǎn)了。
比如,有人看到標(biāo)號(hào)為 ② 的部分,也就是 AOP 的部分,一想著這玩意我眼熟啊,書上寫過(guò) Spring 的事務(wù)是基于 AOP 實(shí)現(xiàn)的,我去看看這部分代碼吧。
當(dāng)你走到 AOP 里面去的時(shí)候,路就開(kāi)始有點(diǎn)走偏了。你明白我意思吧?
即使在這個(gè)過(guò)程中,你翻閱了這部分的源碼,確實(shí)了解到了更多的關(guān)于 AOP 和事務(wù)之間的關(guān)系,但是這個(gè)部分并不解決你“關(guān)于回滾的判斷”這個(gè)問(wèn)題。
然而更多更真實(shí)的情況可能是這樣的,當(dāng)你點(diǎn)到 AOP 這部分的時(shí)候,你一看這個(gè)類名稱是 CglibAopProxy:
你一細(xì)嗦,Cglib 你也熟悉啊,它和 JDK 動(dòng)態(tài)代理是一對(duì)好兄弟,都是老八股了。
然后你可能又會(huì)點(diǎn)擊到 AopProxy 這個(gè)接口,找到 JdkDynamicAopProxy:
接著你恍然大悟:哦,我在什么都沒(méi)有配置的情況下,當(dāng)前版本的 SpringBoot 默認(rèn)使用的是 Cglib 作為動(dòng)態(tài)代理的實(shí)現(xiàn)啊。
誒,我怎么記得我背的八股文默認(rèn)是使用 JDK 呢?
網(wǎng)上查一下,查一下。
哦,原來(lái)是這么一回事兒?。?/p>
- SpringBoot 1.x,默認(rèn)使用的是 JDK 動(dòng)態(tài)代理。
- SpringBoot 2.x 開(kāi)始,為了解決使用 JDK 動(dòng)態(tài)代理可能導(dǎo)致的類型轉(zhuǎn)化異常而默認(rèn)使用 CGLIB。
- 在 SpringBoot 2.x 中,如果需要默認(rèn)使用 JDK 動(dòng)態(tài)代理可以通過(guò)配置項(xiàng)spring.aop.proxy-target-class=false來(lái)進(jìn)行修改,proxyTargetClass配置已無(wú)效。
剛剛提到了一個(gè) spring.aop.proxy-target-class 配置,這是個(gè)啥,咋配置???
查一下,查一下...
喂,醒一醒啊,朋友,走遠(yuǎn)了啊。還記得你調(diào)試源碼的目的嗎?
如果你對(duì)于 AOP 這個(gè)部分感興趣,可以先進(jìn)行簡(jiǎn)單的記錄,但是不要去深入的追蹤。
不要覺(jué)得自己只是隨便看看,不要緊。反正正是因?yàn)檫@些“隨便看看”導(dǎo)致你在源碼里面忙了半天感覺(jué)這波學(xué)到了,但是停下來(lái)一想:我 TM 剛剛看了些啥來(lái)著?我的問(wèn)題怎么還沒(méi)解決?
我為什么要把這部分非常詳盡,甚至于接近啰嗦的寫一遍,就是因?yàn)檫@個(gè)就是初看源碼的朋友最容易犯的錯(cuò)誤。
特別強(qiáng)調(diào)一下:抓住主要矛盾,解決主要問(wèn)題。
好,回到我們通過(guò)調(diào)用棧找到的這個(gè)和事務(wù)相關(guān)的方法中:
org.springframework.transaction.interceptor.TransactionInterceptor#invoke
這個(gè)方法,就是我們要打第二個(gè)斷點(diǎn),或者說(shuō)這才是真正的第一個(gè)斷點(diǎn)的地方。
然后,重啟項(xiàng)目,重新發(fā)起請(qǐng)求,從這個(gè)地方就可以進(jìn)行正向的調(diào)試,也就是從框架代碼一步步的往業(yè)務(wù)代碼執(zhí)行。
比如這個(gè)方法接著往下 Debug,就來(lái)到了這個(gè)地方:
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
找到了這個(gè)地方,你就算是無(wú)限的接近于問(wèn)題的真相了。
這個(gè)部分我肯定會(huì)講的,但是在這里先按下不表,畢竟這并不是本文最重要的東西。
本文最重要的是,我再次重申一遍:我試圖想要教會(huì)你一種閱讀源碼的方式,讓你找到一個(gè)好的切入點(diǎn),或者說(shuō)突破口。
由于這個(gè)案例比較簡(jiǎn)單,所以很容易找到真正的第一個(gè)利于調(diào)試的斷點(diǎn)。
如果遇到一些復(fù)雜的場(chǎng)景、響應(yīng)式的編程、異步的調(diào)用等等,可能會(huì)循環(huán)往復(fù)的執(zhí)行上面的動(dòng)作。
分析調(diào)用棧,打斷點(diǎn),重啟。
再分析調(diào)用棧,再打斷點(diǎn),再重啟。
方法論之死盯日志
其實(shí)我發(fā)現(xiàn)很少有人會(huì)去注意框架打印的日志,就像是很少有人會(huì)去仔細(xì)閱讀源碼上的 Javadoc 一樣。
但是其實(shí)通過(guò)觀察日志輸出,也是一個(gè)很好的尋找閱讀源碼突破口的方式。
我們要做的,就是保證 Demo 盡量的單純,不要有太多的和本次排查無(wú)關(guān)的代碼和依賴引入。
然后把日志級(jí)別修改為 debug:
logging.level.root=debug
接著,就是發(fā)起一次調(diào)用,然后耐著性子去看日志。
還是我們的這個(gè) Demo,發(fā)起一次調(diào)用之后,控制臺(tái)輸出了很多的日志,我給你搞個(gè)縮略圖看看:
我們已知的是這里面大概率是有線索的,有沒(méi)有什么方法盡量快的找出來(lái)呢?
有,但是通用性不強(qiáng)。所以如果經(jīng)驗(yàn)不夠豐富的話,那么最好的方法就是一行行的去找。
前面我也說(shuō)過(guò)了:啃源碼的過(guò)程,一定是非常枯燥的。
所以你一定會(huì)找到這樣的日志輸出:
Acquired Connection [HikariProxyConnection@982684417 wrapping com.mysql.cj.jdbc.ConnectionImpl@751a148c] for JDBC transaction
Switching JDBC Connection [HikariProxyConnection@982684417 wrapping com.mysql.cj.jdbc.ConnectionImpl@751a148c] to manual commit
...
==> Preparing: insert into student ( name, home ) values ( ?, ? )
HikariPool-1 - Pool stats (total=1, active=1, idle=0, waiting=0)
==> Parameters: why(String), 草市街199號(hào)-insertTestNoRollbackFor(String)
<== Updates: 1
...
Committing JDBC transaction on Connection [HikariProxyConnection@982684417 wrapping com.mysql.cj.jdbc.ConnectionImpl@751a148c]
Releasing JDBC Connection [HikariProxyConnection@982684417 wrapping com.mysql.cj.jdbc.ConnectionImpl@751a148c] after transaction
這幾行日志,不就是正對(duì)應(yīng)著 Spring 事務(wù)的開(kāi)啟和提交嗎?
有了日志,我們完全可以基于日志去找對(duì)應(yīng)的日志輸出的地方,比如我們現(xiàn)在要找這一行日志輸出對(duì)應(yīng)的代碼:
o.s.j.d.DataSourceTransactionManager : Acquired Connection [HikariProxyConnection@982684417 wrapping com.mysql.cj.jdbc.ConnectionImpl@751a148c] for JDBC transaction
首先,我們可以根據(jù)日志知道對(duì)應(yīng)輸出的類是 DataSourceTransactionManager 這個(gè)類。
然后找到這個(gè)類,按照關(guān)鍵詞搜索:
不就找到這一行代碼了嗎?
或者我們直接秉承大力出奇跡的真理,來(lái)一個(gè)暴力的全局搜索,也是能搜到這一行代碼的:
再或者修改一下日志輸出格式,把行號(hào)也搞出來(lái)嘛。
當(dāng)我們把日志格式修改為這樣之后:
logging.pattern.cnotallow=%d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger.%M:%L - %msg%n
控制臺(tái)的日志就變成了這樣:
org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin:263 - Acquired Connection [HikariProxyConnection@1569067488 wrapping com.mysql.cj.jdbc.ConnectionImpl@19a49539] for JDBC transaction
很直觀的就看出來(lái)了,這行日志是 DataSourceTransactionManager 類的 doBegin 方法,在 263 行輸出的。
然后你找過(guò)去,發(fā)現(xiàn)沒(méi)有任何毛病,這就是案發(fā)現(xiàn)場(chǎng):
我前面給你說(shuō)這么多,就是為了讓你找到這一行日志輸出的地方。
現(xiàn)在,找到了,然后呢?
然后肯定就是在這里打斷點(diǎn),然后重啟程序,重新發(fā)起調(diào)用了啊。
這樣,你又能得到一個(gè)調(diào)用棧:
然后,你會(huì)從調(diào)用棧中看到一個(gè)我們熟悉的東西:
朋友,這不就和前面寫的“方法論之關(guān)注調(diào)用?!焙魬?yīng)起來(lái)了嗎?
這不就是一套組合拳嗎,不就是沉淀出的可復(fù)用的方法論嗎?
黑話,咱們也是可以整兩句的。
方法論之查看被調(diào)用的地方
除了前面兩種方法之外,我有時(shí)候也會(huì)直接看我要閱讀部分的方法,在框架中被哪些地方調(diào)用了。
比如在我們的 Demo 中,我們要閱讀的代碼非常的明確,就是 @Transactional 注解。
于是直接看一下這個(gè)注解在哪些地方用到了:
有的時(shí)候調(diào)用的地方會(huì)非常的少,甚至只有一兩處,那么直接在調(diào)用的地方打上斷點(diǎn)就對(duì)了。
雖然 @Transactional 注解一眼望去也是有很多的調(diào)用,但是仔細(xì)一看大多是測(cè)試類。排除測(cè)試類、JavaDoc 里面的備注和自己項(xiàng)目中的使用之后,只剩下很明顯的這三處:
看起來(lái)很接近真相,但是很遺憾,這里只是在項(xiàng)目啟動(dòng)的時(shí)候解析注解而已。和我們要調(diào)研的地方,差的還有點(diǎn)遠(yuǎn)。
這個(gè)時(shí)候就需要一點(diǎn)經(jīng)驗(yàn)了,一看苗頭不對(duì),立馬轉(zhuǎn)換思路。
什么是苗頭不對(duì)呢?
你在這幾個(gè)地方大上斷點(diǎn)了,只是在項(xiàng)目啟動(dòng)的過(guò)程中斷點(diǎn)起作用了,發(fā)起調(diào)用的時(shí)候并沒(méi)有在斷點(diǎn)處停下,說(shuō)明發(fā)起調(diào)用的時(shí)候并不會(huì)觸發(fā)這部分邏輯,苗頭不對(duì)。
順著這個(gè)思路想,在我的 Demo 中拋出了異常,那么 rollbackFor 和 noRollbackFor 這兩個(gè)參數(shù)大概率是會(huì)在調(diào)用的時(shí)候被用到,對(duì)吧?
所以當(dāng)你去看 rollbackFor 被調(diào)用的時(shí)候只有我們自己寫的業(yè)務(wù)代碼在調(diào)用:
怎么辦呢?
這個(gè)時(shí)候就要靠一點(diǎn)運(yùn)氣了。
是的,靠運(yùn)氣。
你都點(diǎn)到 rollbackFor 這個(gè)方法來(lái)了,你也看了它被調(diào)用的地方,在這個(gè)過(guò)程中你大概率會(huì)瞟到幾眼它對(duì)應(yīng)的 JavaDoc:
org.springframework.transaction.annotation.Transactional#rollbackFor
然后你會(huì)發(fā)現(xiàn)在 JavaDoc 里面提到了 rollbackOn 這個(gè)方法:
org.springframework.transaction.interceptor.DefaultTransactionAttribute.rollbackOn(Throwable)
到這里一看,你發(fā)現(xiàn)這是一個(gè)接口,它有好多個(gè)實(shí)現(xiàn)類:
怎么辦呢?
早期的時(shí)候,由于不知道具體的實(shí)現(xiàn)類是哪個(gè),我是在每個(gè)實(shí)現(xiàn)類的入口處都打上斷點(diǎn),雖然是笨辦法,但是總是能起作用的。
后來(lái)我才發(fā)現(xiàn),原來(lái)可以直接在接口上打斷點(diǎn):
然后,重啟項(xiàng)目,發(fā)起調(diào)用,第一次會(huì)停在我們方法的入口:
F9,跳過(guò)當(dāng)前斷點(diǎn)之后,來(lái)到了這個(gè)地方:
這里就是我前面在接口上打的方法斷點(diǎn),走到了這個(gè)實(shí)現(xiàn)類中:
org.springframework.transaction.interceptor.DelegatingTransactionAttribute
然后,關(guān)鍵的就來(lái)了,我們又有一個(gè)調(diào)用棧了,又從調(diào)用棧中看到一個(gè)我們熟悉的東西:
朋友,組合拳這不又打起來(lái)了?突破口不就又找到了?
關(guān)于“瞟到幾眼對(duì)應(yīng)的 JavaDoc ,然后就可能找到突破口”的這個(gè)現(xiàn)象,早期對(duì)我來(lái)說(shuō)確實(shí)是運(yùn)氣,但是現(xiàn)在已經(jīng)是一個(gè)習(xí)慣了。一些知名框架的 JavaDoc 真的寫的很清楚的,里面隱藏了很多關(guān)鍵信息,而且是最權(quán)威的正確信息,讀官網(wǎng)文檔,比讀技術(shù)博客穩(wěn)當(dāng)?shù)亩唷?/p>
探索答案
前面我介紹的都是找到代碼調(diào)試突破口的方法。
現(xiàn)在突破口也有了,接下來(lái)應(yīng)該怎么辦呢?
很簡(jiǎn)單,調(diào)試,反復(fù)的調(diào)試。從這個(gè)方法開(kāi)始,一步一步的調(diào)試:
org.springframework.transaction.interceptor.TransactionInterceptor#invoke
如果你真的想要有所收獲的話,這是一個(gè)需要你親自去動(dòng)手的步驟,必須要有逐行閱讀的一個(gè)過(guò)程,然后才能知道大概的處理流程。
我就不進(jìn)行詳細(xì)解讀了,只是把重點(diǎn)給大家畫一下:
框起來(lái)的部分,就是去執(zhí)行業(yè)務(wù)邏輯,然后基于業(yè)務(wù)邏輯的處理結(jié)果,去走不同的邏輯。
拋異常了,走這個(gè)方法:completeTransactionAfterThrowing
正常執(zhí)行完畢了,走這個(gè)方法:commitTransactionAfterReturning
所以,我們問(wèn)題的答案就藏在 completeTransactionAfterThrowing 里面。
繼續(xù)調(diào)試,進(jìn)入這個(gè)方法之后,可以看到它拿到了事務(wù)和當(dāng)前異常相關(guān)的信息:
在這個(gè)方法里面,大體的邏輯是當(dāng)標(biāo)號(hào)為 ① 的地方為 true 的時(shí)候,就在標(biāo)號(hào)為 ② 的地方回滾事務(wù),否則就在標(biāo)號(hào)為 ③ 的地方提交事務(wù):
因此,標(biāo)號(hào)為 ① 的部分就很重要了,這里面就藏著我們問(wèn)題的答案。
另外,在這里多說(shuō)一句,在我們的案例中,這個(gè)方法,也就是當(dāng)前調(diào)試的方法是不會(huì)回滾的:
而這個(gè)方法是會(huì)回滾的:
也就是這兩個(gè)方法在這個(gè)地方會(huì)走不同的邏輯,所以你在調(diào)試的時(shí)候遇到 if-else 就需要注意,去構(gòu)建不同的案例,以覆蓋盡量多的代碼邏輯。
繼續(xù)往下調(diào)試,會(huì)進(jìn)入到標(biāo)號(hào)為 ① 的 rollbackOn 方法里面,來(lái)到這個(gè)方法:
org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn
這里,就藏著問(wèn)題的終極答案,而且這里面的代碼邏輯相對(duì)比較的繞。
核心邏輯就是通過(guò)循環(huán) rollbackRules,這里面裝的是我們?cè)诖a中配置的回滾規(guī)則,在循環(huán)體中拿 ex,也就是我們程序拋出的異常,去匹配規(guī)則,最后選擇一個(gè) winner:
如果 winner 為空,則走默認(rèn)邏輯。如果是 RuntimeException 或者是 Error 的子類,就要進(jìn)行回滾:
如果有 winner,判斷 winner 是否是不用回滾的配置,如果是,則取反,返回 false,表示不進(jìn)行回滾:
那么問(wèn)題的冠軍就在于:winner 怎么來(lái)的?
答案就藏著這個(gè)遞歸調(diào)用中:
一句話描述就是:看當(dāng)前拋出的異常和配置的規(guī)則中的 rollbackFor 和 noRollbackFor 誰(shuí)距離更近。這里的距離指的是父類和子類之間的關(guān)系。
比如,還是這個(gè)案例:
我們拋出的是 RuntimeException,它距離 noRollbackFor=RuntimeException.class 為 0。RuntimeException 是 Exception 的子類,所以距離 rollbackFor = Exception.class 為 1。
所以,winner 是 noRollbackFor,能明白吧?
然后,我們?cè)倏匆幌逻@個(gè)案例:
根據(jù)前面的“距離”的分析,NullPointerException 是 RuntimeException 的子類,它們之間的距離是 1。而 NullPointerException 到 Exception 的距離是 2:
所以,rollbackFor=RuntimeException.class 這個(gè)的距離更短,所以 winner 是 rollbackFor。
而把 winner 放到這個(gè)判斷中,返回是 true:
return !(winner instanceof NoRollbackRuleAttribute);
所以,這就是它為什么會(huì)回滾的原因:
好了,到這里你有可能是暈的,暈就對(duì)了,去調(diào)試這部分代碼,親自摸一遍,你就搞的明明白白了。
最后,再給“死盯日志”的方法論打個(gè)補(bǔ)丁吧。
前面我說(shuō)了,日志級(jí)別調(diào)整到 Debug 也需要會(huì)有意外發(fā)現(xiàn)?,F(xiàn)在,我要再給你說(shuō)一句,如果 Debug 沒(méi)有查到信息,可以試著調(diào)整到 trace:
logging.level.root=trace
比如,當(dāng)我們調(diào)整到 trace 之后,就可以看到“ winner 到底是誰(shuí)”這樣的信息了:
當(dāng)然了,trace 級(jí)別下日志更多了。
所以,來(lái),再跟我大聲的讀一遍:
啃源碼的過(guò)程,一定是非??菰锏?,特別是啃自己接觸不多的框架源碼的時(shí)候,千頭萬(wàn)緒,也得下手去捋,所以一定要耐得住寂寞才行。
作業(yè)
我前面主要是試圖教你一種閱讀源碼時(shí),尋找突破點(diǎn)的技能。這個(gè)突破點(diǎn),說(shuō)白了就是第一個(gè)有效的斷點(diǎn)到底應(yīng)該打在哪里。
你用前面我教的方法,也能把 @Cacheable 和 @Async 都玩明白。因?yàn)樗鼈兊牡讓舆壿嫼?@Transactional 是一樣的。
所以,現(xiàn)在布置兩個(gè)作業(yè)。
拿著這套組合拳,去上手玩一玩 @Cacheable 和 @Async 吧,沉淀出屬于自己的方法論。
@Cacheable:
@Async:
本文轉(zhuǎn)載自微信公眾號(hào)「 why技術(shù)」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 why技術(shù)公眾號(hào)。??
??
??
本文標(biāo)題:我試圖通過(guò)這篇文章,教會(huì)你一種閱讀源碼的方式
標(biāo)題來(lái)源:http://m.fisionsoft.com.cn/article/djhodep.html


咨詢
建站咨詢
