Jetty 类载入问题处理
前幾日使用 Jetty (9.2)部署公司一個(gè) web 項(xiàng)目,這個(gè)項(xiàng)目原本部署在 Tomcat server上,一切正常,可是部署到 Jetty 后,啟動(dòng)報(bào)錯(cuò).關(guān)鍵錯(cuò)誤信息為"java.lang.NoClassDefFoundError: Could not initialize class org.apache.tomcat.jdbc.pool.DataSource"
項(xiàng)目使用了 Tomcat jdbc connection pool 當(dāng)中有兩個(gè) jar 包 tomcat-jdbc.jar 和 tomcat-juli.jar, 后者是前者的 maven 依賴,后者也是 tomcat 的日志抽象層,tomcat server自帶這個(gè) jar 在 tomcat_home 的 bin 文件夾下.依據(jù)異常信息推斷錯(cuò)誤是因?yàn)檩d入和初始化org.apache.tomcat.jdbc.pool.DataSource導(dǎo)致的,為了更easy分析問(wèn)題,我創(chuàng)建了一個(gè)簡(jiǎn)單的 web 項(xiàng)目?jī)H僅依賴于這兩個(gè) jar 包.部署到同一個(gè) Jetty server中,報(bào)錯(cuò)"java.util.ServiceConfigurationError: org.apache.juli.logging.Log: Provider org.eclipse.jetty.apache.jsp.JuliLog not a subtype"
查看源碼org.eclipse.jetty.apache.jsp.JuliLog非常明白是org.apache.juli.logging.Log的子類,但為什么會(huì)報(bào)這種錯(cuò)誤呢,結(jié)合之前的java.lang.NoClassDefFoundError和java.util.ServiceConfigurationError能夠確定問(wèn)題是因?yàn)轭愝d入引起的,依據(jù)對(duì)類載入的了解,同一個(gè)類被不同的類載入器實(shí)例載入得到的 Class 對(duì)象是不同的.所以我判斷可能是因?yàn)?Jetty server使用了不同的類載入器實(shí)例載入了兩個(gè)累,導(dǎo)致繼承關(guān)系不存在了.查看java.util.ServiceConfigurationError相關(guān)的API文檔發(fā)現(xiàn),這個(gè) Error 是在 ServiceLoader載入 Service Provider 時(shí)發(fā)生的.查看 ServiceLoader 的源碼發(fā)現(xiàn)這樣一段
private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype");//我遇到的錯(cuò)誤信息剛好是這里. } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }
為了驗(yàn)證我的猜測(cè),參考這段代碼我寫了個(gè)小程序來(lái)測(cè)試.主要代碼例如以下
public class Main {public static void main(String[] args) throws ClassNotFoundException {final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();final CustomClassLoader customClassLoader= new CustomClassLoader("target/classes", "自己定義載入器", systemClassLoader);//第一個(gè)參數(shù)的路徑是類的編譯路徑//使用系統(tǒng)類載入器載入 Child//由于 Child 依賴 Parent 所以系統(tǒng)類載入器會(huì)自己主動(dòng)載入 Parent,這個(gè)行為與 jetty 的 WebAppClassCloader 相同Class<?> child = Class.forName("Child", true, systemClassLoader);//由于之前載入過(guò),不會(huì)反復(fù)載入直接返回 Parent 類實(shí)例Class<?> parent = Class.forName("Child", true, systemClassLoader);//使用自己定義載入器載入 Child//自己定義載入器會(huì)優(yōu)先嘗試自己載入,失敗后使用父載入器Class<?> customChild = Class.forName("Child", true, customClassLoader); Class<?
> customParent = Class.forName("Parent", true, customClassLoader);//相同不會(huì)反復(fù)載入 //測(cè)試 //相同使用系統(tǒng)載入器載入的兩個(gè)類,繼承關(guān)系正常 System.out.println("parent.isAssignableFrom(child) = " + parent.isAssignableFrom(child));//true //使用自己定義載入器載入的 Child 卻不是系統(tǒng)類載入器載入的 Parent 的子類 System.out.println("parent.isAssignableFrom(customChild) = " + parent.isAssignableFrom(customChild));//false //相同使用自己定義載入器載入的兩個(gè)類,集成關(guān)系正常 System.out.println("customParent.isAssignableFrom(customChild) = " + customParent.isAssignableFrom(customChild));//true } }
這段代碼中 Child 是 Parent 的子類.這個(gè)簡(jiǎn)單的測(cè)試驗(yàn)證了我的猜測(cè).查看文檔發(fā)現(xiàn) Jettty 在9.2版本號(hào)中的 jsp 引擎使用的是 tomcat 的.在 Jetty 的 lib 里面能夠發(fā)現(xiàn)例如以下jar
org.eclipse.jetty.apache-jsp-9.2.3.v20140905.jar
org.eclipse.jetty.orbit.org.eclipse.jdt.core-3.8.2.v20130121.jar
org.mortbay.jasper.apache-el-8.0.9.M3.jar
org.mortbay.jasper.apache-jsp-8.0.9.M3.jar
org.mortbay開頭的兩個(gè) jar 里面是 apache 的 jsp 實(shí)現(xiàn)類當(dāng)中包括org.apache.juli.logging這個(gè)包,org.eclipse.jetty.apache-jsp-9.2.3.v20140905.jar這個(gè) jar 中提供了 logging 的詳細(xì)實(shí)現(xiàn),終于通過(guò) ServiceLoader 載入.問(wèn)題就出在這里,由于我的項(xiàng)目中有 tomcat-juli.jar 當(dāng)中也包括org.apache.juli.logging這個(gè)包.
查看了一下 Jetty 的文檔中有關(guān)類載入的內(nèi)容,發(fā)現(xiàn) Jetty 對(duì)每一個(gè)部署的 web 應(yīng)用使用單獨(dú)的WebAppClassLoader實(shí)例進(jìn)行類載入.通常實(shí)現(xiàn)自己定義類載入器的時(shí)候會(huì)優(yōu)先托付給父載入器(一般為系統(tǒng)類載入器,能夠通過(guò) ClassLoader.getSystemClassLoader() 得到),然后再嘗試自己載入類.但 Jetty 的這個(gè) WebAppClassLoader 正相反,除了對(duì)于系統(tǒng)類和server類(什么是系統(tǒng)類和server類能夠查看文檔),會(huì)優(yōu)先嘗試自己載入,然后才托付父載入器.
依據(jù)這個(gè)行為基本能夠確認(rèn)了,server載入 jsp 引擎是會(huì)使用自己的類載入器載入server lib 中上述的類(org.apache.juli.logging.Log及事實(shí)上現(xiàn)org.eclipse.jetty.apache.jsp.JuliLog),應(yīng)用部署時(shí)會(huì)使用WebAppClassLoader載入應(yīng)用 lib 中的org.apache.juli.logging.Log.載入過(guò)程是這種, WebAppClassLoader實(shí)例載入org.apache.tomcat.jdbc.pool.DataSource,其依賴org.apache.juli.logging.Log,優(yōu)先嘗試自己載入,所以會(huì)從應(yīng)用的 lib 中載入到這個(gè)類,而嘗試載入事實(shí)上現(xiàn)的時(shí)候發(fā)現(xiàn)應(yīng)用 lib 中沒(méi)有,再托付給父類載入器,也就是 Jetty server的載入器,成功載入到org.eclipse.jetty.apache.jsp.JuliLog,這樣就是使用兩個(gè)不同的載入器實(shí)例載入了子類和父類,依據(jù)之前的測(cè)試結(jié)果,兩個(gè)類之間的繼承關(guān)系是不成立的.所以導(dǎo)致發(fā)生錯(cuò)誤.
清楚了問(wèn)題的解決辦法,怎么解決呢?
方案一,在 maven 配置中將 tomcat-juli 的依賴 scope 改為 provided,Jetty server已經(jīng)提供了. 這樣在WebAppClassLoader 嘗試自己載入org.eclipse.jetty.apache.jsp.JuliLog時(shí)會(huì)失敗,進(jìn)而托付父類載入器,這樣org.apache.juli.logging.Log及事實(shí)上現(xiàn)org.eclipse.jetty.apache.jsp.JuliLog兩個(gè)類就是同一個(gè)載入器載入了.
方案二,更改 WebAppClassLoader 的父類載入器的優(yōu)先級(jí),使其優(yōu)先使用父類載入器.詳細(xì)配置方式能夠參考文檔.目標(biāo)是調(diào)用?setParentLoaderPriority(true)
使用這兩個(gè)方案,相同能夠解決原始項(xiàng)目中的問(wèn)題,可是為什么測(cè)試用的簡(jiǎn)單 web 項(xiàng)目和原始項(xiàng)目的錯(cuò)誤信息不同呢?
回到原始項(xiàng)目的錯(cuò)誤信息發(fā)現(xiàn),"java.lang.NoClassDefFoundError: Could not initialize class org.apache.tomcat.jdbc.pool.DataSource"這個(gè)錯(cuò)誤是因?yàn)樵谳d入類的時(shí)候無(wú)法初始化,那么看org.apache.tomcat.jdbc.pool.DataSource中,在類載入后要做的初始化操作有什么,通過(guò)查看源代碼發(fā)現(xiàn)private static final Log log = LogFactory.getLog(DataSource.class);僅僅有這句代碼是須要在類載入后進(jìn)行的初始化,跟蹤這個(gè)語(yǔ)句發(fā)現(xiàn)終于會(huì)進(jìn)入上文中提到的nextService方法,所以根源錯(cuò)誤依舊是上面描寫敘述的.
至此問(wèn)題得到比較圓滿的解釋和解決.
總結(jié):
本文涉及到的知識(shí)點(diǎn)
1.Java虛擬機(jī)的類載入機(jī)制
2.JavaServiceProvider 載入機(jī)制
3.Java 類的初始化過(guò)程
4.Jetty server的配置方式
總結(jié)
以上是生活随笔為你收集整理的Jetty 类载入问题处理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C++ STL 遍历 map 的时候如何
- 下一篇: mysql数据库软件 国产_国产数据库发