1. 程式人生 > 其它 >發現Spring事務的一個實錘bug,官方還拒不承認?你來評評理...

發現Spring事務的一個實錘bug,官方還拒不承認?你來評評理...

你好呀,我是歪歪。

事情是這樣的,上週我正在全神貫注的摸魚,然後有個小夥伴給我發來微信訊息,提出了自己關於事務的一個疑問,並配上兩段程式碼:

先說結論:我認為這是 Spring 事務的一個 bug。但是官方說這隻能算是文件上的缺陷,不能算是程式碼的 bug。

(好吧,我這篇文章寫了好幾天,所以我寫到上面這一句的時候,官方還不承認是 bug,但是寫完之後他們也承認確實是程式碼缺陷。不影響,接著往下看。)

好傢伙,我懂了,一切解釋權歸官方所有。

在開始刨根問底之前,我想先就關於如何提問這個問題掰扯幾句。

我把上面這個讀者的問題截出來,是因為我覺得這個提問簡直就是模板方法般的提問。

給了一段示例程式碼、給了一段原始碼、說明自己的問題、並表明自己已經查詢過但是沒有找到合適的答案。

我讀完他的文字,我很快就能 get 到他的問題是什麼,所以我們之間的交流就非常的高效。

最終我們並沒有討論出一個合理的解釋,於是他去提了一個 issues,希望能得到官方的比較權威的回答。

所以我們的故事就圍繞著這個 issues 開始吧。

舞臺搭建

在正戲開始之前,我先給你把舞臺搭建出來,也就是把 Demo 搞出來。

因為是關於 Spring 事務的問題嘛,所以這個 Demo 主要就是體現出“事務”的應用就行了嘛。

所以 Demo 裡面最核心的東西就是這個部分:

其中涉及到的兩個異常就是簡單的自定義異常:

假設這裡有一個只允許 10-18 歲的使用者使用奇怪的網站,這個部分就代表這個網站的使用者註冊功能。

接著我們往 user_info 表裡面插入一條資料,然後判斷年齡如果大於 18 歲,那麼丟擲 AgeExceptionOver18 異常,表示這個使用者不是目標使用者。

但是你注意,我 @Transactional 註解裡面的 rollbackFor 是 AgeException.class,意思是我並不想回滾這個大於 18 歲的使用者,我還是想把他的註冊資訊儲存下來,只是丟擲一個異常來表示他不是我的目標使用者,

而當我們插入一個年齡小於 10 歲的使用者的時候,會丟擲 AgeException.class,應該把剛剛執行的插入語句給回滾掉,我並不想儲存這部分使用者的資訊。

好的,那麼現在就會有小夥伴問了:小於 10 歲的使用者既然不想儲存,那麼為什麼不在插入之前判斷呢?

很好的問題,實際開發中肯定是要在插入之前判斷的,但是我這裡只是為了演示事務功能,所以我就這樣寫了,咋地了吧!

上面的程式碼,我來搞個介面觸發一下,也就是弄個 Controller 出來:

上面的四個類,就是最關鍵的幾個類,所以我單獨拿出來說一下。

整個專案結構也非常的簡單:

其他的類不關鍵,就不拿出來說了,都是你最拿手的 crud。花五分鐘搭一個這個專案出來不過分吧?中間還能摸兩分鐘的魚。

我把日誌級別調整為 debug 級別,接著把專案跑起來驗證一下功能。

然後呼叫這個連結:

http://127.0.0.1:8085/insertUser?age=8

對應的日誌是這樣的:

可以看到我框起來的部分,首先確認執行了 insert 語句,且 age 確實是為 8。但是最後 Rolling back 了,即回滾了。

為什麼回滾,我們心裡也是門清,因為這裡呼應上了:

接下來試一下 age 為 18 歲的使用者:

http://127.0.0.1:8085/insertUser?age=18

對應的日誌是這樣的:

這沒啥說的,事務成功提交,資料插入成功。是我們預期的結果。

資料庫資料也沒毛病:

然後試一下 age 為 28 歲的使用者。

這個使用者我們的預期是丟擲 AgeExceptionOver18 異常,但是資料得插入成功。

來走一個:

http://127.0.0.1:8085/insertUser?age=28

對應的日誌是這樣的:

首先資料居然回滾了???

異常倒是丟擲來了,但是這也沒呼應上啊!

先不管到底啥原理吧,從我的認知來說,首先我的 @Transactional 註解用法絕對沒有錯,事務配置沒有絕對沒有錯,我的異常也沒有亂拋,你憑什麼給我回滾了?

你還說這不是 bug?

just 改改 documentation 就行了?

言外之意是要“抵賴”,強行從文件上找補嗎?

這不是欺負老實人嗎?

額...等等,我寫這段的時候情況是這樣的。

但是等我寫完這段,第二天再次進 issues 裡面去看,發現事情發生了變化,官方又承認這是一個 bug 了,會在 5.3.x 版本里面修改文件上的描述,會在 6.0 版本里面進行程式碼上的修復。

但是我前面已經鋪墊了這麼多,已經寫好了,我就不改了,就當在這裡留下一個創作痕跡吧,哈哈。

我們接著往下看。

戲劇衝突

一部戲,肯定有它的戲劇衝突,這是它的核心部分。那麼我們 Demo 裡面的核心衝突是什麼呢?

這一小節就先告訴你“戲劇衝突”在哪。

我先問你一個問題:

Spring 管理的事務,預設回滾的異常是什麼呢?

我們帶著這個問題去看原始碼,找到了這個問題的答案,你就能絲滑入戲。

先搞個斷點,把程式跑起來,然後看呼叫棧:

可以看到呼叫棧裡面和事務相關的有這樣一個方法:

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

這就是我們的突破口。

什麼,你問我怎麼一下就找到了這裡來的?

我只能說:熟能生巧而已。

好吧,其實是有技巧的,你可以自己試著去找一下,因為這不是本文重點,所以我就不多說了。

方法執行異常之後,會走到 catch 程式碼塊裡面,下面這一行程式碼就是異常相關處理的入口:

在我們 age=28 的這個場景下,這個方法進來之後,首先 ex 引數就是我們自定義的 AgeExceptionOver18 異常:

我還框起來了一行程式碼:

txInfo.transactionAttribute.rollbackOn(ex)

這一行程式碼你看名字 rollbackOn 也知道是判斷 ex 引數是否匹配指定的回滾異常。

如果匹配呢?如果不匹配呢?

如果匹配,rollback。

如果不匹配,commit。

好了,我們接著往下看。

你會走到這裡來:

org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn

而這個方法,當 winner 為 null 的時候,上面有個註釋,是說沒有匹配到對應的規則。

也就是我們什麼都不配置,預設情況下,winner 就是 null。

那麼下面這行程式碼裡面就藏著我們要找的問題的答案:

return super.rollbackOn(ex);

所以 Spring 管理的事務,預設回滾的異常是什麼呢?

原始碼告訴我:如果當前丟擲的異常屬於 RuntimeException 或者 Error 都會回滾。

前面都是我在鋪路,只是為了把你引到 rollbackOn 方法這個地方來,甚至 super.rollbackOn(ex) 這行程式碼都是煙霧彈,本文中我們完全不必關注。

我們需要關注的,是這個部分:

首先我們明確一下這個 rollbackRules 是啥玩意。

在我們的 Demo 裡面,它就是我們配置在 @Transactional 註解上 rollbackFor 屬性中的 AgeException.class 物件的全路徑名稱:

好,接下來就關鍵了,你一定要打起精神來。

重點關注這一行程式碼:

int depth = rule.getDepth(ex);

來,看一下 rule 和 ex 分別是什麼東西:

rule 裡面的 exceptionName 是我們配置的 AgeException 物件的全路徑名稱。

ex 是我們程式丟擲的 AgeExceptionOver18 異常。

這場戲的核心衝突,就藏在這裡的這個方法裡面:

org.springframework.transaction.interceptor.RollbackRuleAttribute#getDepth(java.lang.Throwable)

好,至此,舞臺搭建完成,核心衝突已經暴露出來。

好戲準備開場。

大幕拉開

你仔細看我框起來的程式碼。

前面的 exceptionClass.getName() 是啥玩意?

它長這樣:

後面的 this.exceptionName 是啥玩意?

它是這個玩意:

接下來,神奇的事情就要發生了,鐵子。

com.example.transactional.exception.AgeExceptionOver18
com.example.transactional.exception.AgeException

雖然這是兩個不同的異常,但是這兩個字串進行 contains 操作,你說是不是返回 true?

所以,這裡的 depth 會返回 0。

那麼這裡的 winner 不會為空

因此這個方法的返回值就會是 true:

還記得我前面說的嗎,這裡返回 true 會執行什麼程式碼?

是不是就 rollback 回滾了?

所以,萬惡之源就是我們大幕拉開的時候就提到的這一段程式碼:

org.springframework.transaction.interceptor.RollbackRuleAttribute#getDepth(java.lang.Class<?>, int)

到這裡,我覺得已經非常明確了:這難道不是 bug 嗎?你強如 Spring 難道還想狡辯?

但是,如果下面這兩個字串進行 equals 操作,你說是不是返回 false,問題就得到解決了?

com.example.transactional.exception.AgeExceptionOver18
com.example.transactional.exception.AgeException

道理是這麼個道理,但是我覺得問題肯定沒這麼簡單。

首先我覺得這裡用 contains 肯定是故意的,但是具體出於什麼目的,我還真不確定。

於是和我討論的讀者提出一個看法,會不會是為了滿足 rollbackForClassName 這個屬性:

因為當我們用 rollbackForClassName 的時候可以用字串陣列的形式去配置多個需要回滾的異常名稱,比如我搞個 NullPointerException:

在正常使用的場景下,我們是可以完成回滾操作的。

對應地方的程式碼的值是這樣的;

java.lang.NullPointerException 字串當然包含了 NullPointerException 字串。所以我們進行回滾嘛。沒毛病。

但是如果我們用 equals 操作,那麼就匹配不上,導致 rollbackForClassName 屬性失效了。

所以把 contains 修改為 equals 屬於拆西牆,補東牆的措施,不可取。

但是 rollbackForClassName 屬性在我們的 Demo 下,也是沒有效果的。

比如我把程式改成這樣,你說,是不是就亂套了?

同樣的道理嘛。

com.example.transactional.exception.AgeExceptionOver18 字串當然包含了 AgeException 字串了。

但是我並不想回滾啊,哥,你好好看看,我丟擲來的異常是 AgeExceptionOver18 呀。

到這裡,我想問題我應該已經描述的非常清楚了,要是你還是沒明白問題是什麼,那你不用往下看了,再看一下“大幕拉開”這一節。

不然後面你很難入戲。

鋪墊一波

為了把真正的問題更好的丟擲來,我必須得先把另外一個相關的問題引出來,作為鋪墊。

首先,我們去 Spring 專案的 issues 裡面搜一下 getDepth 方法所在的 RollbackRuleAttribute 這個類。

看看有沒有相關的蛛絲馬跡,結果如下:

經過分析,對我有幫助的也只有第一條內容。

https://github.com/spring-projects/spring-framework/pull/24682

題目叫做:

Improve javadoc in RollbackRuleAttribute regarding nested classes。
改進 RollbackRuleAttribute 中關於巢狀類的 javadoc。

從題目我們知道這是一次對於文件的改進。

那麼具體是啥改進呢?

可以看到他的描述中也提到了我們前面分析的那一個“萬惡之源”的方法。

關於他具體說了什麼,其實我也不用給你翻譯,直接給你看他提交的程式碼就一目瞭然了:

他主要說個了內部類的問題,而且他這個問題和我們的還有點不一樣。

他的兩個異常類,一個叫 EnclosingException,另一個叫做 EnclosedException,這兩個字串是不存在 contains 關係的。

那麼在內部類的場景下,問題是什麼呢?

我也給你演示一個,你只需要看一眼就明白了,示例程式碼如下:

需要注意的是,我現在的兩個異常是 AgeException 和 AgeOver18Exception,這二者並不存在包含關係。

前面做 Demo 的時候是 AgeExceptionOver18。

AgeOver18Exception
AgeExceptionOver18

別看花眼了。

你看,內部類的時候丟擲異常是這樣的:

throw new AgeException.AgeOver18Exception();

你要是沒回過味兒來,沒關係,斷點一打,程式碼一跑就恍然大悟:

看明白沒,鐵子。內部類丟擲的異常的全路徑名稱是這樣的:

xxx.UserInfoService\$ AgeException$AgeOver18Exception

這不就包含 AgeException 了嗎,不就匹配上了嗎,不就回滾了嗎?

所以,雖然他這個問題的觸發方式和我前面提到的還不一樣,但是“萬惡之源”是一樣的。

那麼解決方案是什麼呢?

僅僅是修改了一下文件,從文件的角度表明了這個情況是會被回滾的:

對應到原始碼,也就是這個地方的註釋:

org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(java.lang.Class<?>)

好,我們現在冷靜的思考一下,這裡僅僅是從文件的角度來修復這個問題,在文件裡面明確說明指定異常的內部類也會被回滾,這個做法對不對?

我認為勉強是可以接受的。

比如,我們知道某個異常類被標記為應該被回滾,那麼這個異常類的子類應該被回滾,這是沒問題的。

我認為內部類和子類應該儲存同樣的邏輯,畢竟它們之前確實存在程式碼上的關聯關係,從這個角度上也說的過去。

畢竟一切解釋權歸官方所有嘛。

到這裡你要記住:

  • RollbackRuleAttribute 類已經因為在回滾異常的判斷上使用 contains 爆出過內部類的問題。
  • 這個問題通過修改 javadoc 描述的方式進行了修復,沒有修改任何程式碼。
  • 這個解決方案勉強說的過去。

好了,鋪墊完成了。

好戲上演

我再次在 issues 裡面搜尋 RollbackRuleAttribute,會發現多了一條內容:

前面其實我刻意把這一條內容給隱藏了,因為這個 issues 就是和我聊天的讀者提的。

這裡的示例程式碼就是文章最開頭出現的程式碼。

好戲就藏在這個 issues 裡面的,一起看一下官方是怎麼“反覆橫跳”。

https://github.com/spring-projects/spring-framework/issues/28098

首先,是一個叫做 snicoll 的哥們把這個 issues 的標題改了一下:

去掉了開頭的“Bug”,這也很正常,屬於提問不標準,現階段只是提問者認為是一個 bug,你這樣取名字,個人主觀意識太過強烈,這樣不好。

主要是你知道修改題目的 snicoll 是誰嗎?

別問,問就是大佬,Spring 和 Spring Boot專案核心維護人員。

然後隔了幾天,這個問題的標題又被另外一個大佬,簡單的修改了一下:

僅僅是把 use contains 修改為了 uses contains(),把 equals 修改為了 equals()

這個小細節完美的體現了 Spring 框架的嚴謹之處,可以說是非常的嚴謹了。

還沒進入到問題解答環節,先把問題的“錯別字”給修改了。

接著就進入了官方答疑環節。

說了下面這麼大一堆內容,但是根本不要慌,你知道的我的 English level 是非常的 high 的。這一堆內容分為三大部分,我會一點點的給你說明白:

首先是第一部分:

一上來就是個英語長句,但是根本不要怕。

你看他先是簡明扼要的提到了一個短語“by design”,也就是“設計如此”。

整個翻譯過來大概就是這樣的。

這個地方我們要用 contains() 方法呢?這其實是經過考慮的。

那麼是基於什麼考慮呢?

在 XML 配置檔案中,使用者通常指定自定義異常型別的簡單名稱,而不是全路徑類名。

啥意思呢?

他給了一個文件中的 xml 配置示例:

那我現在基於我們的 Demo 也搞一個 xml 配置嘛,回到若干年前的基於 xml 配置的方式:

這裡我也不得不感慨一句:以前基於 xml 開發的時候是真的麻煩,每次都要去系統專案裡面拷一份配置出來,所以我還是很感謝 SpringBoot 的出現的。

這裡他想表達一個什麼意思呢?

在我的 xml 配置中,關於 rollback-for 屬性。他提到的 simple name 就是 AgeException。而 fully qualified class name 就是 com.example.transactional.exception.AgeException。

就是說這裡是不限制使用者填什麼的。

如果使用者填的是 simple name,我們也應該讓其生效,所以必須要使用 contains() 方法。

以我理解,這個地方和 @Transactional 註解裡面的 rollbackForClassName 屬性的用法是一樣,而這是一個歷史遺留問題,是當年一個不好的設計。

但是我認為不能說考慮不周,畢竟別人也很難想到你會按照那麼奇怪的方式去命名異常類啊!

總之這一段話他解釋了為什麼會用 contains() 方法,為什麼不能用 equals() 方法。

和我們前面分析的基本一致,只是我們沒有想到 XML 的配置方式。

第二段,他開始從文件的角度來解釋這個問題。

叫我們關注一下 RollbackRuleAttribute 上的 Javadoc 描述。

這裡有一個“NB”,不是我們常常說的牛逼,而是一個縮寫:

你看,又在我這裡學到一個用不上的英文知識。

我們接著看,主要關注我劃線的兩句。

第一句是說:由於使用的 contains() 方法,“Exception” 幾乎可以匹配任何規則,並且可能會隱藏其他規則。但是“java.lang.Exception”這個全路徑的字串,那麼匹配範圍就小了很多了。

第二句是說:由於使用的 contains() 方法,對於 “BaseBusinessException” 等不尋常的異常名稱,不需要使用類的全路徑名稱。

所以,第二段他想表達的是:文件上我們已經說過了,對於匹配規則,要仔細思考,要非常的小心:

u1s1,他確實寫了,但是你覺得你會看嗎?

第三段就很簡單了:

看到 its subclasses, and its nested classes. 就知道這是我們前面“鋪墊一波”小節說過的部分。

所以你現在知道我為什麼給你鋪墊了吧?

如果不給你鋪墊一波,你突然看到一個內部類的單詞 nested classes,你說你一下反應得過來嗎?

你要永遠相信我的行文結構。

好了,現在看另外一句我標註的地方,翻譯過來是說:在當前的實現中最後一句話並沒有遵守。

這裡的“最後一句話”就是指 RollbackRuleAttribute 的 Javadoc 的最後一句,也就是 ... its subclasses, and its nested classes 這句。

當然沒遵守了。

我寫的 Demo 裡面的兩個異常即不存在子類父類的關係,也不存在內部類的關係。

所以我覺得很納悶:這個 Javadoc 和我的問題之間並不存在關係,或者說並不衝突啊。前面我也說了,關於這部分的 Javadoc 我覺得是沒有毛病的。如果你想要從修改文件的角度來解決這個問題,也不應扯到子類,內部類啥的,應該是完全另起一行才對。

但是具體怎麼解決,他並沒有立即表態,而是把這個 issues 放到了 Triage Queue 裡面:

Queue,佇列,你肯定都知道。

Triage 是個啥?

我也不知道,於是我也學到了一個新單詞:

也就是說官方把這個 issues 放到了“待分類的”一個佇列裡面,說明他目前是瞭解到了問題的所在,但是具體應該怎麼解決,還沒有定論,有待商榷。

隔了一天這個老哥又來表態了,開始“橫跳”:

他說他又想了一下,需要更正他之前的說法:RollbackRuleAttribute(Class) 建構函式的 Javadoc 是 mostly correct,也就是基本沒毛病的。需要改進的是關於回滾規則上的描述。

總之他還是想從文件的角度來修復這個問題。

但是解釋了我前面的疑惑:即使從修改文件的角度來解決這個問題,也不應扯到子類,內部類啥的,應該是完全另起一行才對。

他這裡的“回滾規則”也就是“另起一行”。

接著,他對任務的狀態進行了流轉:

從“待分類”移動到了“文件”的標籤下。

然後表示在 5.3.17 這個里程碑版本中會進行修復:

同時,再次修改了 issues 的標題:

Transaction rollback rules may result in unintentional matches for similarly named exceptions
事務回滾規則可能會導致無意中匹配到名稱相似的例外情況

其實如果讓我來處理這個問題,我大概率也是會從文件的角度入手,並且最多加一點提醒日誌,畢竟這是你使用不規範導致的。

而且我文件上已經說明有“坑”了,你自己沒看踩進去了,這怪不得我呀。

但是在和這個讀者表達了我的觀點之後,他提出的不一樣的看法:

他覺得使用者大多並不關注日誌,主張丟擲異常的方式進行強提醒:

於是他在 issues 上表達了自己的看法:

他覺得需要更精準的匹配規則,大多數人是不看文件的。

接著官方採納了他的意見,並把該需求移動到了 6.0.0-M3 這個里程碑的版本中去實現:

他的具體回覆如下:

他說:老鐵,我同意你關於“需要更精準的匹配規則”的觀點。

我們會修復 5.3.x 的文件描述。

然後在 6.0 版本中,我們會改進一版程式碼。

具體來說是這樣的:

  • 如果異常模式是以字串形式提供的。例如,在 XML 配置中或通過 @Transactional(rollbackForClassName = "example.CustomException") 配置,那麼現有的 contains() 邏輯將繼續被使用。
  • 如果一個具體的異常型別是以類引用的形式提供的。例如,通過 @Transactional(rollbackFor = example.CustomException.class),將會有新的邏輯。它完全採用配置上提供的型別資訊,從而避免了在 example.CustomException(沒有2)被提供為異常型別時,與 example.CustomException2 的意外匹配。

他這裡提到的 CustomException 和 CustomException2,其實是他的測試用例裡面的程式碼。類比於我們前面的 AgeException 和 AgeExceptionOver18 這兩個異常。

接著,他對這個 issues 進行了重新分類,從“文件”型別,修改為了“enhancement”型別:

enhancement,是個四級詞語,背一下,會考:

表示這個 issues 需要通過修改程式碼來使健壯性更強。

然後再次修改了標題:

對於事務回滾規則,應該使用異常的型別資訊,而不是用模式匹配。

本來故事到這裡都已經是大結局了,我寫到這裡的時候就準備收尾了。

想著收尾不著急,先睡一覺再說。

結果...

第二天早上起來,他!又!更!新!了!

我還得補一段內容。

最後一集

早上起來,我一重新整理頁面,發現官方針對這個 issues 進行了最後一次提交:

這次 issues 的標題,最後定格為:

Support type-safe transaction rollback rules
支援型別安全的事務回滾規則

而這次對應的程式碼提交連結是這樣的:

https://github.com/spring-projects/spring-framework/commit/c1033dbfb3609f3b3fe002d7b582b3302620c05a

裡面寫了很長一段的內容,來描述這次提交的背景,但是基本上都是我前面寫過的東西的總結:

結合我前面寫的東西,我給你翻譯翻譯:

首先我覺得是在事務模組裡面創造一個新的概念:type-safe rollback rules,型別安全的回滾規則。

在這次提交之前,只有一種事務回滾機制, Pattern-based rollback rules,即基於匹配模式的回滾規則。

而官方說基於匹配模式的回滾規則,會帶來三種意料之外的匹配情況:

  • 不同包中的相同命名的異常類,會被意外匹配上。比如,example.client.WebException 和 example.server.WebException 都會與 “WebException” 模式匹配。
  • 在同一個包中有類似命名的異常,這裡說的相似是指當一個給定的異常名稱是以另一個異常的名稱開頭時。例如:example.BusinessException 和 example.BusinessExceptionWithDetails 都與 “example.BusinessException”模式匹配。
  • 巢狀異常,也就是當一個異常類被宣告在另一個異常類裡面的時候。例如:example.BusinessException 和 example.BusinessException$NestedException 都會與 “example.BusinessException” 匹配上。

第一種沒啥說的,請使用全路徑名稱去避免。

第二種就是我們文章中的例子,需要通過修改程式碼解決。

第三種內部類的情況我也在前面鋪墊過了。但是當時的解決方案是僅增加文件中對應的描述。

但是現在,你看他怎麼說的:

這次的提交可以防止後兩種情況的意料之外的匹配。也就是說這次提交不僅修復了我們的問題,還修復了內部類的問題。

那麼怎麼修復的呢?

首先是在 RollbackRuleAttribute 類裡面新增了一個 exceptionType 欄位:

然後在構造方法裡面對其進行賦值:

核心程式碼變成了這樣:

當 exceptionType 欄位,即全路徑名稱不為空的時候,使用 equals() 方法,也就是type-safe rollback rules。否則使用 contains() 方法,也就是 Pattern-based rollback rules。

另外,關於 Javadoc 上的很多描述也發生了變化,我就不一一舉例了,強烈建議你自己去看看這次提交。

我只特別拿出一處變化來給你看看:

去掉了“內部類”,改成了“型別安全”。

至此,問題得到解決。

但是在 XML 裡面或者用 @Transactional 註解裡面的 rollbackForClassName 屬性,也就是使用匹配模式的時候,還是會有意料之外的匹配情況。

官方:反正我在文件上說清楚了,你要是還踩坑,那就怪得不我了?

最後,再插一個關於程式設計規範的事兒。

你想這次這個問題完全是因為你有兩個這樣的異常類名稱引起的:

AgeException
AgeExceptionOver18

而對於異常類,我們都約定成俗的要求必須以“Exception”結尾。

包括阿里巴巴 Java 開發手冊在命名風格里面也特意提到了這一點,且是強制要求:

所以,如果我們都遵守這個規則,大家就相安無事。

那麼,這個故事最後告訴我們一個什麼道理呢?

它告訴我們...

它告訴我們規則就是拿來打破的,如果你不打破規則,永遠也踩不到這個坑,也就不會推動 Spring 的改動。

打破規則,這是你的一小步,卻是開源世界的一大步。

所以,兄弟們,鐵子們,不要墨守成規,要打破...

最後,文章首發於公眾號[why技術],歡迎關注,第一時間接收最新文章。