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