遷移至 Java 17 或我如何學會停止擔心並愛上 NoSuchMethodError

https://iconsolutions.com/blog/upgrading-to-java17/

Icon 最近釋出了 IPF 2024.1 版本,其中將所有組件升級至 Java 17,並要求最低版本為 Java 17。我是這項工作的首席工程師,現在它已經面世,我決定花點時間寫寫這個經驗,包括:

背後的背景

需要避免的一些陷阱

在 Java 17/Spring 6/Spring Boot 3 中需要特別留意的一些新事物

……如果你也打算走上相似的旅程,無論你使用 IPF 或否,這對你可能會有所幫助。

除了為了我們的客戶而進行正常的安全性和可支援性外,引起我們注意的主要原因是 2022 年 Juergen Hoeller 宣布的消息,即 Spring 6+ 和 Spring Boot 3+ 將只支援 Java 17+。此外,隨著 Java 12→17 中一些很酷的新功能(例如 records、sealed types、switch expressions)無法使用,部分人開始抱怨。為了避免被排擠,我提出我們應該允許一些時間來針對 IPF 進行升級並向客戶傳達變更。

此外,Java 8 到 17 之間有關於世代性垃圾收集 (GC) 的改進,即便只是使用默認的 GC 配置。Oracle JDK GC 團隊的 Stefan Johnasson 在一篇部落格文章中描述了 JDK 在 Java 8 到 Java 17 之間達成的 GC 改進。

我們從 Java 11 升級到 17,而不是 21 的主要原因是這是一個最低公分母:我們在去年 10 月/11 月左右對所有客戶進行了調查,所有客戶回答他們支援 Java 17(調查約 2 年前發布),但只有一兩個表示支援 Java 21(調查約 2 個月前發布)。

另一個原因是我們無法從 Java 21 中受益最大的變更虛擬執行緒:這是因為 Akka – 我們用於事件溯源、叢集以及其他事務的非阻塞、消息導向、異步架構 – 在 2024 年 5 月 17 日釋出的 24.05 版本中才認證支援 Java 21。我們不打算作為此次升級的一部分獨立編寫虛擬執行緒代碼,所以 Java 17 讓我們與 Spring 6/Spring Boot 3 保持相容,幫助我避免走向絕路。

在付款處理領域,風險太大,在成為早期採用者的風險方面並不會得到獎勵。讓其他人(其他領域和使用案例)來解決問題!

建設性的陷阱

這裡有危險!我在這裡有點太細節。如果你對這些並不感興趣,你可以跳過這一部分。

那麼,我該做些什麼呢?我們使用 Maven 進行構建和依賴管理,所以我肯定可以根據 StackOverflow 上的小說更改一些神奇的屬性和插件配置,將 Java 版本從 11 改為 17。好,搞定了,然後…

[ERROR] Failed to execute goal [org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.14.0:generate (default) on project shared-domain: Execution default of goal org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.14.0:generate failed: An API incompatibility was encountered while executing org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.14.0:generate failed: An API incompatibility was encountered while executing org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.14.0:generate: java.lang.ExceptionInInitializerError: null

這個令人愉快且信息豐富的錯誤消息讓我踏上了旅程。這個 maven-jaxb2-plugin 已經紛裂成碎片。

由於我們基於 ISO20022,而我們支援的大多數支付方案也是基於 ISO20022,所以我們必須處理大量的 XML。這意味著我們有相當多的 JAXB Maven 插件執行來接受 XML schema 並生成值對象。探查異常,我們得到了一大堆我不會在這裡重複的胡言亂語,但根本原因是我將一直遇到的三騎士之一:

騎士

錯誤消息

含義

1

java.lang.NoSuchMethodError: [某個類別或方法]

我期望這個類別或方法,但它不在這裡了(或其簽名已更改)

2

java.lang.reflect.InaccessibleObjectException: Unable to make [a] accessible: module [b] does not “opens ” to [d]

升級至 Java 17 很有趣

3

(其他任何)

神秘有趣時間!

所以我必須升級這個和許多其他插件和依賴項。正如你所想像的,擊敗表中的一個騎士只會召喚出另一個騎士。所以我必須一直四處查找,參考許多 GitHub 問題、StackOverflow 頁面、論壇帖子等,直到我能夠獲得一套能夠在不引起編譯、構建或運行爆炸的依賴關係和 BOM。

不可避免的後果

我遇到的主要問題源於 Spring 6/Boot 3 引入的各種破壞性變更,例如從 Java EE 轉移到 Jakarta EE。這導致了相當多的破壞。為了應對這一點,除了將 Java EE 注釋更新到 Jakarta EE,我們甚至不得不要求我們的合作夥伴 Lightbend 發布一個特殊版本的 Alpakka 來支援 Jakarta Messaging!在修改了幾個專案之後,我實際上開發了一個糟糕的別名(出於懼怕嚴懲,我不會在這裡重複)在每個專案的根目錄下運行,這個別名將執行以下操作:

將 javax.* 替換為 jakarta.* – 有時會失敗,但我想說這是一個正面的改變?

刪除對 Java 11/Java 版本/編譯器插件等的任何引用(這是集中的,不應該在個別專案中)

刪除任何我們在正常項目創建工作的一部分中創建的奇怪插件定義覆蓋的引用,這導致問題

我們為客戶撰寫了一份關於升級至 Java 17/Spring 6/Spring Boot 3 的指南,其中包含了我遇到的所有問題,客戶可能也會在他們的 IPF 實施中遇到。我不會在這裡重複它,因為這篇文章已經夠長了,但你可以點擊這裡查看包含這些建議的遷移文檔。

“為什麼不使用……?”

我知道 OpenRewrite 的存在-但老實說,我所遇到的大部分挑戰並不是它設計來解決的(無論如何都不容易),我認為它引入的問題比它解決的問題多(因為它對於父專案的 dependencyManagement 不夠意識,將文件放在奇怪的地方)。我不認為我們的項目是小眾或非尋常的,但花費時間去理解 OpenRewrite 的奇異行為要比編寫上面提到的別名更費時。

結論和得到的經驗

我不知道對你來說是否表現出來,親愛的讀者,這對我來說是一次治療。我覺得這比我最初想像的要困難一點,但對我和幫助我的同事來說,這是一次很好的學習經歷。我們也獲得了一些見解,這將使未來的升級更容易:

不要在項目中隨機使用插件執行和依賴項:我注意到工程師傾向於在嘗試解決問題時這樣做,並從 StackOverflow/Baeldung/mkyong 等地方復制粘貼。這些答案和文章通常已經多年沒更新,使用過時的插件執行或依賴版本。有一個控制所有插件版本、依賴版本等的單一父專案,避免在子依賴中有任何特定版本覆蓋。可以參考 FasterXML/oss-parent 作為一個示例。

保持依賴關係和漏洞的掌控:確保版本-特別是插件的版本-始終保持更新。這將使進行類似本文所述的更大範圍的升級時更加順利。考慮使用平台工具,例如 GitHub 上的 Dependabot,或其他平台上的 renovate,它們可以執行類似的工作。

最低的童軍規則:這是一個有爭議的觀點嗎?當然你應該把事情比你找到它的時候留得更整潔,但將專案升級到 Java 17 後,我收到了很多 IntelliJ 的可愛嘗試:“可以改成記錄!”,“可以使用文本塊!”,“使用模式變量!”,“替換為 switch 表達式!”-這些都是很好的建議,但你很快就會被它們分散注意力,這會使工作增加一點時間。此外,我會說這幾乎不適用於童軍規則:將代碼遷移到更現代的形式算不上把它“留得比原來更整潔”嗎?

希望你喜歡這個故事,帶有它的扭轉與轉彎。當我們的客戶採用 IPF 2024.1 並沿著他們的 Java 17 之旅遇到問題時,我們會密切關注。我可能會將他們鏈接到這個治療會話!

via Hacker News

June 25, 2024 at 02:32AM

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *