工作中特別是中介軟體落地時常常會遇到jar包衝突的情況,本篇文章將分析目前業界的Jar包隔離解決方案。
一.jar包衝突的本質
Java 應用程式因某種因素,載入不到正確的類而導致其行為跟預期不一致。
二. jar包衝突的兩種情況
第一類jar包衝突問題(同一jar包版本不同)
- 應用程式依賴的同一個 Jar 包出現了多個不同版本,並選擇了錯誤的版本而導致 JVM 載入不到需要的類或載入了錯誤版本的類。
- 出現該問題的三個必要條件:
- 依賴樹中出現了同一個jar包的多個版本。
- 該jar包的多個版本之間介面發生了變化(類名,方法簽名變化,方法行為變化)
- maven 的仲裁機制選擇了錯誤的版本
第二類jar包衝突問題(不同jar包的同一類版本不同)
- 同樣的類(類的全限定名完全一樣)出現在多個不同的依賴 Jar 包中,即該類有多個版本,並由於 Jar 包載入的先後順序(Maven的路徑最短和覆蓋優先原則)導致 JVM 載入了錯誤版本的類。如:假設有 A 、 B 、 C 三個jar包,由於 Jar 包依賴的路徑長短、宣告的先後順序或檔案系統的檔案載入順序等原因,類載入器首先從 Jar 包 A 中載入了該類後,就不會載入其餘 Jar 包中的這個類了。
- 出現該問題的三個必要條件:
- 同一類M出現在了兩個(或兩個以上)不同的jar包A、B中。
- 類M在A、B中有差異,行為不同。
- 載入的類M不是我們想要的。
三.解決方案
方法一:手動排查
- 根據異常堆疊資訊確定導致衝突的類名。
- 通過mvn dependency:tree -Dverbose -Dincludes=<groupId>:<artifactId> 檢視是哪個地方引入的jar包的版本。
- 如果是第一類 Jar 包衝突,則可用< excludes>排除不需要的 Jar 包版本或者在依賴管理<dependencyManagement>申明版本。
- 如果是第二類Jar包衝突,如果可以排除,則用 <excludes> 排掉不需要的那個 Jar 包,若不能排,則需考慮 Jar 包的升級或換個別的 Jar 包。
方法二:通過自定義ClassLoader實現隔離 在部落格 Java 中隔離容器的實現 中提到了將每個jar包視為多個bundle,通過自定義classloader隔離執行,並且還可以實現多個jar包共享一個類。 該Demo通過啟動一個KContainer類執行,KContainer類中主要包含一個BundleList和SharedClassList。每一個Bundle代表一個jar包或者class路徑,Bundle類包含一個自定義的BundleClassLoader類(繼承UrlClassLoader),由於不同BundleBundleClassLoader不同,可以實現隔離執行,而這個BundleClassLoader需要傳入一個SharedClassList,classloader在載入一個類時,如果沒有載入到,則可以從外部傳進來的SharedClassList中載入,這樣就實現了多個jar包共享一個類。
protected Class<?> findClass(String name) throws ClassNotFoundException {
logger.debug(“try find class {}”, name);
Class<?> claz = null;
try {
claz = super.findClass(name);
} catch (ClassNotFoundException e) {
claz = null;
}
if (claz != null) {
logger.debug(“load from class path for {}”, name);
return claz;
}
//如果沒有載入到,從共享的類中載入
claz = sharedClasses.get(name);
if (claz != null) {
logger.debug(“load from shared class for {}”, name);
return claz;
}
logger.warn(“not found class {}”, name);
throw new ClassNotFoundException(name);
}
複製程式碼
需要共享出去給別人用的類可以通過在類路徑下通過一個properties檔案指定,在loadBundle的時候載入進SharedClassList。
方法三:輕量級隔離容器SOFAArk SOFAArk同樣也是使用不同的類載入器載入衝突的三方依賴包,進而做到在同一個應用執行時共存。 SOFAArk通過Ark Plugin區分應用中哪些依賴包是需要單獨的類載入器載入。藉助 SOFABoot 官方提供的 maven 打包外掛,開發者可以把若干普通的 JAR 包打包成 Ark Plugin 供應用依賴或者把普通的 Java 模組改造成 Ark Plugin。應用使用新增 maven 依賴的方式引入 Ark Plugin,執行時,SOFAArk 框架會自動識別應用的三方依賴包中是否含有 Ark Plugin,進而使用單獨的類載入器載入。其執行時邏輯圖如下:
- SOFAArk 容器處於最底層,負責啟動應用。
- 每個 Ark Plugin 都由 SOFAArk 容器使用獨立的類載入器載入,相互隔離。
- 應用業務程式碼及其他非 Ark Plugin 的普通三方依賴包,統稱為 Ark Biz。需要依賴下層的Ark Plugin。
在Ark Plugin的POM檔案中,會配置匯出類和匯入類的配置。匯出類即把 Ark Plugin 中的類匯出給 Ark Biz 和其他 Ark Plugin 可見。對於 Ark Plugin 來說,如果需要使用其他 Ark Plugin 的匯出類,必須宣告為自身的匯入類。
方法四:阿里的Pandora隔離容器 阿里的Pandora是閉源的,網上資料比較少。 可以從阿里的一次演講PPT上得知,Pandora仍然是基於ClassLoader實現的 Pandora這類的隔離容器的缺點:
- 使用方式複雜,難以理解。
- 啟動慢,使用者無法按需選擇
- 除錯困難
- 部署和運維困難
下一篇文章將著重分析螞蟻金服的SOFA-ARK容器的使用和原始碼分析