本文介紹了Spring Boot Runnable JAR無法找到通過java.system.class.loader JVM參數設置的類加載器的處理方法,對大家解決問題具有一定的參考價值,需要的朋友們下面隨著小編來一起學習吧!
問題描述
在如下的模塊結構中:
項目
|-公共模塊
|-app模塊
在app模塊將公共模塊作為依賴項的情況下,我有一個在公共模塊中定義的定制類加載器類。應用程序模塊-Djava.system.class.loader=org.project.common.CustomClassLoader
JVM參數設置為使用公共模塊中定義自定義類加載器。
在IDEA中運行一個Spring Boot項目,這可以完美地工作。找到自定義類加載器,將其設置為系統類加載器,一切正常。
編譯一個可運行的JAR(使用沒有任何定制屬性的默認Spring-Boot-maven-plugin),JAR本身擁有所有類,并且在它的lib目錄中是具有定制類加載器的公共JAR。但是,使用-Djava.system.class.loader=org.project.common.CustomClassLoader
運行JAR會導致以下異常
java.lang.Error: org.project.common.CustomClassLoader
at java.lang.ClassLoader.initSystemClassLoader(java.base@12.0.2/ClassLoader.java:1989)
at java.lang.System.initPhase3(java.base@12.0.2/System.java:2132)
Caused by: java.lang.ClassNotFoundException: org.project.common.CustomClassLoader
at jdk.internal.loader.BuiltinClassLoader.loadClass(java.base@12.0.2/BuiltinClassLoader.java:583)
at jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(java.base@12.0.2/ClassLoaders.java:178)
at java.lang.ClassLoader.loadClass(java.base@12.0.2/ClassLoader.java:521)
at java.lang.Class.forName0(java.base@12.0.2/Native Method)
at java.lang.Class.forName(java.base@12.0.2/Class.java:415)
at java.lang.ClassLoader.initSystemClassLoader(java.base@12.0.2/ClassLoader.java:1975)
at java.lang.System.initPhase3(java.base@12.0.2/System.java:2132)
為什么會發生這種情況?是否因為在Runnable JAR中,類加載器類位于lib目錄中的jar中,所以類加載器試圖在將lib類添加到類路徑之前進行設置?除了將類加載器從公共模塊移動到所有其他需要它的模塊之外,我還能做什么嗎?
編輯:我嘗試將自定義類加載器類從公共模塊移動到應用程序,但仍然收到相同的錯誤。這是怎么回事?
推薦答案
在IDEA中運行一個Spring Boot項目,這可以完美地工作。找到自定義類加載器,將其設置為系統類加載器,一切正常。
因為IDEA將模塊放在類路徑上,并且其中一個模塊包含自定義類加載器。
是否因為在可運行的JAR中,類加載器類位于lib目錄中的jar中,因此在將lib類添加到類路徑之前嘗試設置類加載器?
差不多吧。庫類沒有添加到類路徑中,但可運行的Spring Boot應用程序自己的自定義類加載器知道在哪里找到它們以及如何加載它們。
要更深入地了解java.system.class.loader
,請閱讀ClassLoader.getSystemClassLoader()
的Javadoc(添加了枚舉后略微重新格式化):
如果第一次調用此方法時定義了系統屬性
java.system.class.loader
,則該屬性的值將被視為將作為系統類加載器返回的類的名稱。
該類是使用默認系統類加載器加載的,并且必須定義一個公共構造函數,該構造函數接受用作委托父級的類型ClassLoader
的單個參數。
然后使用此構造函數以默認系統類加載器作為參數創建實例。
結果類加載器被定義為系統類加載器。
在構造過程中,類加載器要特別注意避免調用getSystemClassLoader()
。如果檢測到系統類加載器的循環初始化,則引發IllegalStateException
。
這里的決定性因素是#3:用戶定義的系統類加載器由默認的系統類加載器加載。當然,后者不知道如何從嵌套JAR中加載內容。只有在JVM完全初始化并啟動了Spring Boot的特殊應用程序類加載器之后,才能讀取這些嵌套的JAR。
即您遇到了雞和蛋的問題:為了在JVM初始化期間找到您的自定義類加載器,您需要使用尚未初始化的Spring Boot Runnable JAR類加載器。
如果您想知道上面所描述的Javadoc在實踐中是如何實現的,請查看OpenJDKsource code of ClassLoader.initSystemClassLoader()
。
除了將類加載器從公共模塊移動到所有其他需要它的模塊之外,我還能做什么嗎?
如果您堅持使用Runnable JAR,即使這樣也無濟于事。您可以執行以下任一操作:
運行您的應用程序,而不是將其壓縮到可運行的JAR中,而是將其作為一個普通的Java應用程序運行,所有應用程序模塊(尤其是包含自定義類加載器的模塊)都位于類路徑上。
將您的自定義類加載器提取到可運行JAR外部的單獨模塊中,并在運行可運行JAR時將其放在類路徑上。
通過Thread.setContextClassLoader()
左右設置您的自定義類加載器,而不是嘗試將其用作系統類加載器(如果這是一個可行的選項)。
更新2020-10-28:在可執行Jar格式文檔中,我在"Executable Jar Restrictions"下找到了:
系統類加載器:啟動的應用程序在加載類時應該使用
Thread.getContextClassLoader()
(大多數庫和框架默認這樣做)。嘗試使用ClassLoader.getSystemClassLoader()
加載嵌套的JAR類失敗。java.util.Logging
始終使用系統類加載器。因此,您應該考慮不同的日志記錄實現。
這證實了我在上面所寫的內容,特別是我關于使用線程上下文類加載器的最后一個要點。
這篇關于Spring Boot Runnable JAR無法找到通過java.system.class.loader JVM參數設置的類加載器的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,