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






