Java Class Loader 與 Maven Shade Plugin

ChatGPT generated 



Java Class Loader 是 Java Runtime Environment 的一部分,它動態地將 Java 類別加載到 Java Virtual Machine 中。通常,只有在需要時才會加載類別。Java 運行時系統不需要知道文件和文件系統,因為這些都被委派給了類別加載器。

在 Java 語言中,軟體庫通常被打包在 JAR 文件中。庫可以包含不同類型的對象。JAR 文件中包含的最重要類型的對象是 Java 類別。類別可以被視為一個有名稱的程式碼單元。類別加載器負責定位庫,讀取其內容,並加載庫中包含的類別。這種加載通常是 "按需" 進行的,也就是說,直到程式呼叫該類別時才會進行。給定名稱的類別只能被給定的類別加載器加載一次

每個 Java 類別都必須由類別加載器加載。此外,Java 程式可能會使用外部庫(也就是,由程式的作者以外的人寫的和提供的庫),或者至少部分由多個庫組成。

當 JVM 啟動時,會使用三個類別加載器:

  1. Bootstrap class loader
  2. Extensions class loader
  3. System class loader

Bootstrap class loader 加載位於 <JAVA_HOME>/jre/lib(或對於 Java 9 及以上版本,位於 <JAVA_HOME>/jmods)目錄中的核心 Java 庫。這個類別加載器是 JVM 的核心部分,是用原生碼寫的。
Extensions class loader 加載在擴展目錄(<JAVA_HOME>/jre/lib/ext,或由 java.ext.dirs 系統屬性指定的任何其他目錄)中的程式碼。
System class loader 加載在 java.class.path 上找到的程式碼,該路徑映射到 CLASSPATH 環境變量。


當 JVM 需要找到一個類別時,它會遵循以下的步驟:

  1. Bootstrap ClassLoader:首先,Bootstrap ClassLoader 會嘗試在 JVM 的 bootstrap classpath 中找到這個類別。這通常包括像 <JAVA_HOME>/jre/lib/rt.jar 這樣的核心 Java 類別庫。Bootstrap ClassLoader 是用原生碼實現的,並且是 JVM 中的最高級別的類別加載器。
  2. Extension ClassLoader:如果 Bootstrap ClassLoader 找不到該類別,那麼 Extension ClassLoader 會嘗試在擴展目錄(例如 <JAVA_HOME>/jre/lib/ext)中找到這個類別。
  3. Application ClassLoader:如果 Extension ClassLoader 也找不到該類別,那麼 Application ClassLoader(也稱為 System ClassLoader)會嘗試在應用的 classpath 中找到這個類別。這包括由 CLASSPATH 環境變量,-cp 或 -classpath 命令行選項,或者是應用的 Manifest 文件中的 Class-Path 屬性指定的所有目錄和 JAR 文件。

如果所有這些類別加載器都找不到該類別,那麼 JVM 會拋出一個 ClassNotFoundException。

在這個過程中,每個類別加載器都會遵循所謂的 "委託模型"。這意味著在一個類別加載器嘗試加載一個類別之前,它首先會將這個任務委託給它的父類別加載器。這確保了類別加載的唯一性和安全性,因為這樣可以確保核心 Java 類別不能被應用的類別覆蓋。

至於如何決定從哪個 JAR 文件中加載類別,這實際上取決於類別的完全限定名稱以及 JAR 文件在 classpath 中的順序。JVM 會按照 classpath 中的順序,從每個 JAR 文件中嘗試加載類別,直到找到匹配的類別為止。如果有多個 JAR 文件包含同一個類別,那麼 JVM 通常會使用它在 classpath 中首次找到的那個版本。

在 Java 中,class 載入的行為主要由 java.lang.ClassLoader class 定義。這個 class 提供了一個方法 loadClass(String name),它實現了 class 載入的基本機制。這個方法首先檢查該 class 是否已經被載入,如果已經被載入,則直接返回該 class。如果還沒有被載入,則該方法會將載入任務委託給 parent class loader。只有當 parent class loader 無法載入該 class 時,該方法才會嘗試自己載入該 class。


當你遇到多個 JAR 文件包含相同類別的情況:

  1. 檢查並更新你的 CLASSPATH:確保 CLASSPATH 中的 JAR 文件順序正確,並且你需要的類別版本在前面的 JAR 文件中。
  2. 移除重複的 JAR 文件:如果可能,從你的應用和 CLASSPATH 中移除包含重複類別的 JAR 文件。
  3. 使用 Maven 或 Gradle 這樣的依賴管理工具:這些工具可以幫助你管理你的依賴並避免版本衝突。例如,Maven 的 "dependency:tree" 命令可以幫助你找出哪些依賴包含了相同的類別。
  4. 使用 Maven Shading Plugin:這個插件可以將你的應用的所有依賴打包到一個 uber-JAR 中,並且可以選擇性地將包名重新命名(或 "shade")以避免命名空間衝突。
  5. 使用 OSGi 或 Java 9 的模組系統:這些技術可以讓你更精細地控制哪些類別和 JAR 文件被加載。
  6. 使用自定義的類別加載器:在某些情況下,你可能需要實現你自己的類別加載器來解決這個問題。這通常需要較深的 Java 知識,並且可能會引入其他的複雜性。

Maven shading plugin example
  1. 這個 repo 包含兩個 Maven module:example-lib 和 example-main。
  2. 兩個 module 都使用了 Google 的 Guava lib,但是版本不同。example-lib 使用的是 Guava 31,而 example-main 使用的是 Guava 16。
  3. 在 example-lib module 中,有一個名為 Preconditions31 的 class,它使用了 Guava 31 的 Preconditions class。
  4. 在 example-main module 中,有一個名為 Hello 的 class,它同時使用了 Guava 16 的 Preconditions class 和 example-lib module 中的 Preconditions31 class。
  5. 這個 repo 的主要目的是演示如何使用 Maven 的 Shade 插件來解決 JAR 包中的 class 衝突問題。
  6. 在這個例子中,Shade 插件被用來將 example-lib module 中的 Guava 31 的 Preconditions class 重新命名為 shaded.example.lib.Preconditions31。
  7. 通過這種方式,example-main module 可以同時使用 Guava 16 和 Guava 31 的 Preconditions class,而不會產生衝突。

沒有留言:

張貼留言

別名演算法 Alias Method

 題目 每個伺服器支援不同的 TPM (transaction per minute) 當 request 來的時候, 系統需要馬上根據 TPM 的能力隨機找到一個適合的 server. 雖然稱為 "隨機", 但還是需要有 TPM 作為權重. 解法 別名演算法...