Jar包衝突解決方案調研

Hugo_Gao發表於2018-08-07

工作中特別是中介軟體落地時常常會遇到jar包衝突的情況,本篇文章將分析目前業界的Jar包隔離解決方案。

一.jar包衝突的本質

Java 應用程式因某種因素,載入不到正確的類而導致其行為跟預期不一致。

二. jar包衝突的兩種情況

第一類jar包衝突問題(同一jar包版本不同)

  1. 應用程式依賴的同一個 Jar 包出現了多個不同版本,並選擇了錯誤的版本而導致 JVM 載入不到需要的類或載入了錯誤版本的類。
  2. 出現該問題的三個必要條件:
  • 依賴樹中出現了同一個jar包的多個版本。
  • 該jar包的多個版本之間介面發生了變化(類名,方法簽名變化,方法行為變化)
  • maven 的仲裁機制選擇了錯誤的版本

第二類jar包衝突問題(不同jar包的同一類版本不同)

  1. 同樣的類(類的全限定名完全一樣)出現在多個不同的依賴 Jar 包中,即該類有多個版本,並由於 Jar 包載入的先後順序(Maven的路徑最短和覆蓋優先原則)導致 JVM 載入了錯誤版本的類。如:假設有 ABC 三個jar包,由於 Jar 包依賴的路徑長短、宣告的先後順序或檔案系統的檔案載入順序等原因,類載入器首先從 Jar 包 A 中載入了該類後,就不會載入其餘 Jar 包中的這個類了。
  2. 出現該問題的三個必要條件:
  • 同一類M出現在了兩個(或兩個以上)不同的jar包A、B中。
  • 類M在A、B中有差異,行為不同。
  • 載入的類M不是我們想要的。

三.解決方案

方法一:手動排查

  1. 根據異常堆疊資訊確定導致衝突的類名。
  2. 通過mvn dependency:tree -Dverbose -Dincludes=<groupId>:<artifactId> 檢視是哪個地方引入的jar包的版本。
  3. 如果是第一類 Jar 包衝突,則可用< excludes>排除不需要的 Jar 包版本或者在依賴管理<dependencyManagement>申明版本。
  4. 如果是第二類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,進而使用單獨的類載入器載入。其執行時邏輯圖如下:

image-20180807120802894

  1. SOFAArk 容器處於最底層,負責啟動應用。
  2. 每個 Ark Plugin 都由 SOFAArk 容器使用獨立的類載入器載入,相互隔離。
  3. 應用業務程式碼及其他非 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這類的隔離容器的缺點:

image-20180807120821154

  1. 使用方式複雜,難以理解。
  2. 啟動慢,使用者無法按需選擇
  3. 除錯困難
  4. 部署和運維困難

下一篇文章將著重分析螞蟻金服的SOFA-ARK容器的使用和原始碼分析

相關文章