日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
我試圖通過這篇文章,教會(huì)你一種閱讀源碼的方式

你好呀,我是歪歪。

在將樂等地區(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ì)制作按需開發(fā)網(wǎng)站,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),品牌網(wǎng)站建設(shè),全網(wǎng)營銷推廣,外貿(mào)網(wǎng)站制作,將樂網(wǎng)站建設(shè)費(fèi)用合理。

是的,正如標(biāo)題描述的這樣,我試圖通過這篇文章,教會(huì)你如何閱讀源碼。

事情大概是這樣的,前段時(shí)間,我收到了一個(gè)讀者發(fā)來的類似于這樣的示例代碼:

他說他知道這三個(gè)案例的回滾情況是這樣的:

  • insertTestNoRollbackFor:不會(huì)回滾
  • insertTestRollback:會(huì)回滾
  • insertTest:會(huì)回滾

他說在沒有執(zhí)行代碼之前,他也知道前兩個(gè)為什么一個(gè)不會(huì)回滾,一個(gè)會(huì)回滾。因?yàn)閽伋龅漠惓:?@Transactional 里面的注解呼應(yīng)上了。

但是第三個(gè)到底會(huì)不會(huì)回滾,沒有執(zhí)行之前,他不知道為什么會(huì)回滾。執(zhí)行之后,回滾了,他也不知道為什么回滾了。

我告訴他:源碼之下無秘密。

讓他去看看這部分源碼,理解它的原理,不然這個(gè)地方拋出一個(gè)其他的異常,又不知道會(huì)不會(huì)回滾了。

但是他說他完全不會(huì)看源碼,找不到下手的角度。

所以,就這個(gè)問題,我打算寫這樣的一篇文章,試圖教會(huì)你一種閱讀源碼的方式。讓你找到一個(gè)好的切入點(diǎn),或者說突破口。

但是需要事先說明的是,閱讀源碼的方式非常的多,這篇文章只是站在我個(gè)人的角度介紹閱讀源碼的眾多方式中的一種,滄海一粟,就像是一片樹林里面的一棵樹的樹干上的一葉葉片的葉脈中的一個(gè)小分叉而已。

對(duì)于啃源碼這件事兒,沒有一個(gè)所謂的“一招吃遍天下”的秘訣,如果你非要讓我給出一個(gè)秘訣的話,那么就只有一句話:

啃源碼的過程,一定是非常枯燥的,特別是啃自己接觸不多的框架源碼的時(shí)候,千頭萬緒,也得下手去捋,所以一定要耐得住寂寞才行。

然后,如果你非得讓我再補(bǔ)充一句的話,那么就是:

調(diào)試源碼,一定要親!自!動(dòng)!手!只是去看相關(guān)的文章,而沒有自己一步步的去調(diào)試源碼,那你相當(dāng)于看了個(gè)寂寞。

親自動(dòng)手的第一步就是搞個(gè) Demo 出來。用“黑話”來說,這個(gè) Demo 就是你的抓手,有了抓手你才能打出一套理論結(jié)合實(shí)際的組合拳。抓手多了,就能沉淀出可復(fù)用的方法論,最終為自己賦能。

搭建 Demo

所以,第一步肯定是先把 Demo 給搭建起來,項(xiàng)目結(jié)構(gòu)非常的簡(jiǎn)單,標(biāo)準(zhǔn)的三層結(jié)構(gòu):

主要是一個(gè) Controller,一個(gè) Service,然后搞個(gè)本地?cái)?shù)據(jù)庫給接上,就完全夠夠的了:

Student 對(duì)象是從表里面映射過來的,隨便弄了兩個(gè)字段,主要是演示用:

就這么一點(diǎn)代碼,給你十分鐘,你是不是就能搭建好了?中間甚至還能摸幾分鐘魚。

要是只有這么一點(diǎn)東西的、極其簡(jiǎn)單的 Demo 你都不想自己親自動(dòng)手搭一下,然后自己去調(diào)試的話,僅僅是通過閱讀文章來肉眼調(diào)試,那么我只能說:

在正式開始調(diào)試代碼之前,我們還得明確一下調(diào)試的目的:想要知道 Spring 的 @Transactional 注解對(duì)于異常是否應(yīng)該回滾的判斷邏輯具體是怎么樣的。

帶著問題去調(diào)試源碼,是最容易有收獲的,而且你的問題越具體,收獲越快。你的問題越籠統(tǒng),就越容易在源碼里面迷失。

方法論之關(guān)注調(diào)用棧

自己 Debug 的過程就是不斷的打斷點(diǎn)的過程。

我再說一次:自己 Debug 的過程就是不斷的打斷點(diǎn)的過程。

打斷點(diǎn)大家都會(huì)打,斷點(diǎn)打在哪些地方,這個(gè)玩意就很講究了。

在我們的這個(gè) Demo 下,第一個(gè)斷點(diǎn)的位置非常好判斷,就打在事務(wù)方法的入口處:

一般來說,大家調(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)試的過程中,一個(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)處“往回看”的過程。

當(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)單的回顧一下你來調(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)清晰,很多人在源碼里面迷失的原因就是不知不覺間被源碼牽著走遠(yuǎn)了。

比如,有人看到標(biāo)號(hào)為 ② 的部分,也就是 AOP 的部分,一想著這玩意我眼熟啊,書上寫過 Spring 的事務(wù)是基于 AOP 實(shí)現(xiàn)的,我去看看這部分代碼吧。

當(dāng)你走到 AOP 里面去的時(shí)候,路就開始有點(diǎn)走偏了。你明白我意思吧?

即使在這個(gè)過程中,你翻閱了這部分的源碼,確實(shí)了解到了更多的關(guān)于 AOP 和事務(wù)之間的關(guān)系,但是這個(gè)部分并不解決你“關(guān)于回滾的判斷”這個(gè)問題。

然而更多更真實(shí)的情況可能是這樣的,當(dāng)你點(diǎn)到 AOP 這部分的時(shí)候,你一看這個(gè)類名稱是 CglibAopProxy:

你一細(xì)嗦,Cglib 你也熟悉啊,它和 JDK 動(dòng)態(tài)代理是一對(duì)好兄弟,都是老八股了。

然后你可能又會(huì)點(diǎn)擊到 AopProxy 這個(gè)接口,找到 JdkDynamicAopProxy:

接著你恍然大悟:哦,我在什么都沒有配置的情況下,當(dāng)前版本的 SpringBoot 默認(rèn)使用的是 Cglib 作為動(dòng)態(tài)代理的實(shí)現(xiàn)啊。

誒,我怎么記得我背的八股文默認(rèn)是使用 JDK 呢?

網(wǎng)上查一下,查一下。

哦,原來是這么一回事兒?。?/p>

  • SpringBoot 1.x,默認(rèn)使用的是 JDK 動(dòng)態(tài)代理。
  • SpringBoot 2.x 開始,為了解決使用 JDK 動(dòng)態(tài)代理可能導(dǎo)致的類型轉(zhuǎn)化異常而默認(rèn)使用 CGLIB。
  • 在 SpringBoot 2.x 中,如果需要默認(rèn)使用 JDK 動(dòng)態(tài)代理可以通過配置項(xiàng)spring.aop.proxy-target-class=false來進(jìn)行修改,proxyTargetClass配置已無效。

剛剛提到了一個(gè) spring.aop.proxy-target-class 配置,這是個(gè)啥,咋配置?。?/p>

查一下,查一下...

喂,醒一醒啊,朋友,走遠(yuǎn)了啊。還記得你調(diào)試源碼的目的嗎?

如果你對(duì)于 AOP 這個(gè)部分感興趣,可以先進(jìn)行簡(jiǎn)單的記錄,但是不要去深入的追蹤。

不要覺得自己只是隨便看看,不要緊。反正正是因?yàn)檫@些“隨便看看”導(dǎo)致你在源碼里面忙了半天感覺這波學(xué)到了,但是停下來一想:我 TM 剛剛看了些啥來著?我的問題怎么還沒解決?

我為什么要把這部分非常詳盡,甚至于接近啰嗦的寫一遍,就是因?yàn)檫@個(gè)就是初看源碼的朋友最容易犯的錯(cuò)誤。

特別強(qiáng)調(diào)一下:抓住主要矛盾,解決主要問題。

好,回到我們通過調(diào)用棧找到的這個(gè)和事務(wù)相關(guān)的方法中:

org.springframework.transaction.interceptor.TransactionInterceptor#invoke

這個(gè)方法,就是我們要打第二個(gè)斷點(diǎn),或者說這才是真正的第一個(gè)斷點(diǎn)的地方。

然后,重啟項(xiàng)目,重新發(fā)起請(qǐng)求,從這個(gè)地方就可以進(jìn)行正向的調(diào)試,也就是從框架代碼一步步的往業(yè)務(wù)代碼執(zhí)行。

比如這個(gè)方法接著往下 Debug,就來到了這個(gè)地方:

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

找到了這個(gè)地方,你就算是無限的接近于問題的真相了。

這個(gè)部分我肯定會(huì)講的,但是在這里先按下不表,畢竟這并不是本文最重要的東西。

本文最重要的是,我再次重申一遍:我試圖想要教會(huì)你一種閱讀源碼的方式,讓你找到一個(gè)好的切入點(diǎn),或者說突破口。

由于這個(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í)通過觀察日志輸出,也是一個(gè)很好的尋找閱讀源碼突破口的方式。

我們要做的,就是保證 Demo 盡量的單純,不要有太多的和本次排查無關(guān)的代碼和依賴引入。

然后把日志級(jí)別修改為 debug:

logging.level.root=debug

接著,就是發(fā)起一次調(diào)用,然后耐著性子去看日志。

還是我們的這個(gè) Demo,發(fā)起一次調(diào)用之后,控制臺(tái)輸出了很多的日志,我給你搞個(gè)縮略圖看看:

我們已知的是這里面大概率是有線索的,有沒有什么方法盡量快的找出來呢?

有,但是通用性不強(qiáng)。所以如果經(jīng)驗(yàn)不夠豐富的話,那么最好的方法就是一行行的去找。

前面我也說過了:啃源碼的過程,一定是非??菰锏?。

所以你一定會(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ù)的開啟和提交嗎?

有了日志,我們完全可以基于日志去找對(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)鍵詞搜索:

不就找到這一行代碼了嗎?

或者我們直接秉承大力出奇跡的真理,來一個(gè)暴力的全局搜索,也是能搜到這一行代碼的:

再或者修改一下日志輸出格式,把行號(hào)也搞出來嘛。

當(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

很直觀的就看出來了,這行日志是 DataSourceTransactionManager 類的 doBegin 方法,在 263 行輸出的。

然后你找過去,發(fā)現(xiàn)沒有任何毛病,這就是案發(fā)現(xiàn)場(chǎng):

我前面給你說這么多,就是為了讓你找到這一行日志輸出的地方。

現(xiàn)在,找到了,然后呢?

然后肯定就是在這里打斷點(diǎn),然后重啟程序,重新發(fā)起調(diào)用了啊。

這樣,你又能得到一個(gè)調(diào)用棧:

然后,你會(huì)從調(diào)用棧中看到一個(gè)我們熟悉的東西:

朋友,這不就和前面寫的“方法論之關(guān)注調(diào)用棧”呼應(yīng)起來了嗎?

這不就是一套組合拳嗎,不就是沉淀出的可復(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)目中的使用之后,只剩下很明顯的這三處:

看起來很接近真相,但是很遺憾,這里只是在項(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)的過程中斷點(diǎn)起作用了,發(fā)起調(diào)用的時(shí)候并沒有在斷點(diǎn)處停下,說明發(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è)方法來了,你也看了它被調(diào)用的地方,在這個(gè)過程中你大概率會(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),雖然是笨辦法,但是總是能起作用的。

后來我才發(fā)現(xiàn),原來可以直接在接口上打斷點(diǎn):

然后,重啟項(xiàng)目,發(fā)起調(diào)用,第一次會(huì)停在我們方法的入口:

F9,跳過當(dāng)前斷點(diǎn)之后,來到了這個(gè)地方:

這里就是我前面在接口上打的方法斷點(diǎn),走到了這個(gè)實(shí)現(xiàn)類中:

org.springframework.transaction.interceptor.DelegatingTransactionAttribute

然后,關(guān)鍵的就來了,我們又有一個(gè)調(diào)用棧了,又從調(diào)用棧中看到一個(gè)我們熟悉的東西:

朋友,組合拳這不又打起來了?突破口不就又找到了?

關(guān)于“瞟到幾眼對(duì)應(yīng)的 JavaDoc ,然后就可能找到突破口”的這個(gè)現(xiàn)象,早期對(duì)我來說確實(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)在突破口也有了,接下來應(yīng)該怎么辦呢?

很簡(jiǎn)單,調(diào)試,反復(fù)的調(diào)試。從這個(gè)方法開始,一步一步的調(diào)試:

org.springframework.transaction.interceptor.TransactionInterceptor#invoke

如果你真的想要有所收獲的話,這是一個(gè)需要你親自去動(dòng)手的步驟,必須要有逐行閱讀的一個(gè)過程,然后才能知道大概的處理流程。

我就不進(jìn)行詳細(xì)解讀了,只是把重點(diǎn)給大家畫一下:

框起來的部分,就是去執(zhí)行業(yè)務(wù)邏輯,然后基于業(yè)務(wù)邏輯的處理結(jié)果,去走不同的邏輯。

拋異常了,走這個(gè)方法:completeTransactionAfterThrowing

正常執(zhí)行完畢了,走這個(gè)方法:commitTransactionAfterReturning

所以,我們問題的答案就藏在 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)為 ① 的部分就很重要了,這里面就藏著我們問題的答案。

另外,在這里多說一句,在我們的案例中,這個(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 方法里面,來到這個(gè)方法:

org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn

這里,就藏著問題的終極答案,而且這里面的代碼邏輯相對(duì)比較的繞。

核心邏輯就是通過循環(huán) rollbackRules,這里面裝的是我們?cè)诖a中配置的回滾規(guī)則,在循環(huán)體中拿 ex,也就是我們程序拋出的異常,去匹配規(guī)則,最后選擇一個(gè) winner:

如果 winner 為空,則走默認(rèn)邏輯。如果是 RuntimeException 或者是 Error 的子類,就要進(jìn)行回滾:

如果有 winner,判斷 winner 是否是不用回滾的配置,如果是,則取反,返回 false,表示不進(jìn)行回滾:

那么問題的冠軍就在于:winner 怎么來的?

答案就藏著這個(gè)遞歸調(diào)用中:

一句話描述就是:看當(dāng)前拋出的異常和配置的規(guī)則中的 rollbackFor 和 noRollbackFor 誰距離更近。這里的距離指的是父類和子類之間的關(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ǔ)丁吧。

前面我說了,日志級(jí)別調(diào)整到 Debug 也需要會(huì)有意外發(fā)現(xiàn)?,F(xiàn)在,我要再給你說一句,如果 Debug 沒有查到信息,可以試著調(diào)整到 trace:

logging.level.root=trace

比如,當(dāng)我們調(diào)整到 trace 之后,就可以看到“ winner 到底是誰”這樣的信息了:

當(dāng)然了,trace 級(jí)別下日志更多了。

所以,來,再跟我大聲的讀一遍:

啃源碼的過程,一定是非??菰锏模貏e是啃自己接觸不多的框架源碼的時(shí)候,千頭萬緒,也得下手去捋,所以一定要耐得住寂寞才行。

作業(yè)

我前面主要是試圖教你一種閱讀源碼時(shí),尋找突破點(diǎn)的技能。這個(gè)突破點(diǎn),說白了就是第一個(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ān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 why技術(shù)公眾號(hào)。??

??

??


當(dāng)前標(biāo)題:我試圖通過這篇文章,教會(huì)你一種閱讀源碼的方式
文章URL:http://www.5511xx.com/article/djhodep.html