天天看點

主流web容器(jetty,tomcat,jboss)的classloader機制對比和相關問題分析背景jboss4.05 classloader機制最後(相關問題分析)

     前段時間一直在做應用容器的遷移,将公司的應用容器從jboss,tomcat統一遷移到jetty。在整個遷移過程中遇到最多的潛在問題還是在classloader機制上,這裡記錄一下希望能對大家有所幫助,避免重複走彎路。

啥都不說,先來看下遇到的幾個問題,比較糾結的問題。

可以說基本都是對應的class , method等找不到,或者類沖突等問題,一看就是比較典型的classloader引發的問題。

這裡早期對jboss classloader幾點配置做了下說明,可以參見: http://agapple.iteye.com/blog/791940

為了和tomcat,jetty有更明顯的對比,這裡就主要介紹三個參數代碼層面上的實作:

相信這幾個參數大家都應該比較熟知,配置項在 deploy/jbossweb-tomcat55.sar/meta-inf/jboss-service.xml中

下面循着代碼來看一下jboss的相關實作:

代碼入口: org.jboss.web.tomcat.tc5.tomcat5, 這裡的三個配置都對應于tomcat5類的屬性,預設值就是目前配置的值。

tomcat5會建立一個deploy進行war包的裝載,tomcatdeployer(繼承于abstractwebdeployer)

最後通過mbean調用,将classloader設定給對應的tomcat上下文對象: "org.apache.catalina.core.standardcontext";

說明:

webctxloader: 一個對jboss ucl classloader的一個代理而已,setwarurl也隻是将war資源加入到目前jboss的ucl classloader去裝載

webapploader: 對tomcat webapploader的一個封裝, 同時設定filteredpackages給tomcat webapploader進行class控制。

java2classloadingcompliance是針對usejbosswebloader=false時而言,是通過設定tomcat webappclassloader的是否delegate進行控制classloader,實作child first/parent first。

java2classloadingcompliance在usejbosswebloader=true時,不會生效,會被強制設定為false,具體可看webctxloaders,實作了loader接口,getclassloader()傳回的是一個ctxloader對jboss webloader的一個包裝。

filteredpackages目前是通過tomcat的classloader機制上進行控制,是以隻能是在usejbosswebloader=false時有效,因為依賴了tomcat的實作。

目前在jboss4.05上不支援在在war包下web-inf/jboss-web.xml的配置,在jboss4.20以後才支援,可見https://jira.jboss.org/browse/jbas-3047?subtaskview=all

jboss 5.0以後,就沒有usejbosswebloader這一配置項了,實作方式也不是delegate到tomcat,而是獨立的一個classloader,統一處理。

jboss classloader機制大家也不必太刻意的去學習,隻要适當的了解基本使用。隻能說一切都是浮雲浮雲,随時都可能被改變。

tomcat相比于jboss4.05概念上簡介了很多,不過tomcat 6版本相比于tomcat 5有一些變化,少了一些shared lib庫的概念。

 一個樹狀結構,相信大家差不多都知道tomcat預設是以child first裝載class,優先載入web中的class類,找不到才會去裝載公用類。

下面就來看一下代碼的一些實作: 

tomcat啟動入口,通過分析standardcontext.start()

如果getloader為空,則建立一個新的webapploader,注意這也是jboss設定tomcat loader的入口,進而替換預設的tomcat classloader。

webapploader通過在startinternal啟動了一個新的webappclassloader

最後就是webappclassloader的loadclass方法了,具體的類裝載政策。

下面來看一下,loadclass的相關邏輯: 

(0.1)先檢查class是否已經被裝載過,findloadedclass

(0.2)通過system classloader裝載系統class,是以這裡會優先裝載jdk的相關代碼,這個很重要,也很特别。

如果是delegate=true并且不是filterpackage清單,則采用parent first,否則采用child first。

相關代碼:

delegate(child first/parent first),預設值為false,即為child first

packagetriggers,執行child first時,排除的package清單,如果比對了package,即時為delegate=false,也會優先執行parent first政策。

tomcat的classloader機制在處理class裝載時,會優先嘗試使用system進行裝載,這樣可以優先使用jdk的相關類,這也是問題2的原因。

jetty與tomcat相比,沒有太大上的差別,也是有parent first/child first的概念,filter package等。但還是有些小差別,那先看一下代碼:jetty與tomcat相比,沒有太大上的差別,也是有parent first/child first的概念,filter package等。但還是有些小差別,那先看一下代碼:

jetty啟動入口org.eclipse.jetty.server.server,通過deploymentmanager進行webapps目錄的加載

具體war包加載是通過webappprovider,通過createcontexthandler建立應用上下文webappcontext。

webappcontext在初始化時,建立webappclassloader

最後就是webappclassloader的loadclass方法了。

了解了這些基本概念後,jetty的loadclass方法,邏輯比較好了解:

如果屬于system_class或者_parentloaderpriority=true,并且不是server_class,優先采用parent first進行裝載

否則采用child first,先由webappclassloader進行裝載,沒找到class再交由父cl裝載

 _parentloaderpriority: 對應child first/parent first。

_serverclasses : jetty系統實作類,jetty認為服務實作類,必須在web-inf/lib , web-inf/classes優先加載。

__dftsystemclasses :  系統類,類似于tomcat的filterpackage,優先采取parent first進行裝載。

_parentloaderpriority的變量,可以通過環境變量org.eclipse.jetty.server.webapp.parentloaderpriority=false/true

server_class和system_class,可以通過設定server attributes

容器

jboss(4.05)

tomcat(6.0.30)

jetty(7.1.20)

支援child/parent first設定(預設值)

java2classloadingcompliance=false

delegate=false

_parentloaderpriority=false

過濾package配置

filteredpackages

預設值: javax.servlet,org.apache.commons.logging

packagetriggers

預設配置:org.apache.commons.logging

systemclasses

預設配置:java. 

javax.

org.xml.

org.w3c.

org.apache.commons.logging.

org.eclipse.jetty.continuation.

org.eclipse.jetty.jndi.

org.eclipse.jetty.plus.jaas.

org.eclipse.jetty.websocket.

org.eclipse.jetty.servlet.defaultservlet.

特殊性

1. usejbosswebloader=false時,過濾packages才能生效

2. usejbosswebloader=true時,不支援過濾packages

3. jboss 5.0以後usejbosswebloader參數将不支援

1. 在執行child/parent判斷之前,會委托system classloader裝載系統class,比如jdk的lib庫

1. 多了一個serverclass配置,如果是serverclass優先采用child first

2. systemclass預設的配置,多了javax,org.xml,org.w3c配置。

相關文檔

svn url : http://anonsvn.jboss.org/repos/jbossas/tags/jboss_4_0_5_ga_cp18

jboss社群classloader文檔: http://community.jboss.org/wiki/classloadingconfiguration

svn url : http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk

官方classloader機制: http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html

svn url : http://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/tags/jetty-7.2.0.v20101020/

classloader 官方文檔: http://docs.codehaus.org/display/jetty/classloading

  是一個jar sealed問題, 官方說明: http://download.oracle.com/javase/tutorial/deployment/jar/sealman.html

說白了就是jdk在jar層面上做的一層控制,避免出現兩個不同版本的實作類同時在應用中被裝載。

理清了sealed的含義,再看一下出錯的堆棧:com.sun.media.jai.util,這個類是jai相關處理

jai_core.jar

jai_codec.jar

jai_imageio.jar

幾個jar包的meta-inf/manifest.mf中都定義了sealed :true。而我們的應用中剛好有兩個jar包,那為什麼在jboss運作中沒問題,jetty運作卻出了問題。

最後的原因:

1. 我們使用的是javax.media.jai.jai.create(string opname,parameterblock args),該類剛好依賴了com.sun.media.jai.util.imaginglistenerimpl等相關類。

2. 注意看jetty的特殊性,針對javax.media.jai.jai是屬于systemclass,是以會優先使用jdk去裝載,而com.sun則通過應用容器裝載,很巧的是公司的jdk下的jre/lib/ext下剛好增加了jai的相關jar,最終導緻了sealed沖突。

解決方案:

   處理起來相對暴力,增加配置systemclass配置,添加jai的相關package,所有的jai都采取parent first加載。

xml加載的問題,原因也跟jetty的特殊性相關。大家都知道xml解析類有很多種

xerces

crimson

jdk rt.jar

j2ee.jar

真tmd令人糾結啊,應用容器中一旦同時依賴了這幾個xml類庫,那麻煩問題就來了。這個saxparserfactory.newinstance(string factoryclassname, classloader classloader)在jdk1.6.18版本中有,而在其他的幾個包中卻沒有該接口方法。

該問題的原因:

原先應用運作的是tomca,也因為tomcat容器的特殊性,會優先通過system classloader優先裝載,正好saxparserfactory類屬于jdk的庫,是以也正好裝載了jdk的類

jetty容器因為特殊systemclass配置,針對javax.打頭的package采用parent first,是以這時裝載了jdk saxparserfactory

最後jboss運作時,因為我們使用的是usejbosswebloader=true,是以會優先裝載應用中的lib,至于用哪個那就看機率了,一般按classpath裝載lib的順序。是以這時沒有該方法,就抛出了對應的錯誤

處理方式也比較粗暴,如果該方法存在歧義,那幹脆不用這方法不就得了,最後換用了無參的newinstance()方法。但這時得注意了,不同xml類庫,對應的xml impl預設配置不一樣。比如jdk的配置:就會以他自己的為預設值

是以最後為了統一使用,通過設定環境變量:

 該問題是由問題2引出的,正因為設定了xml解析器為xerces,導緻在jetty啟動的時候出現了一些問題。這次主要的原因就跟共享lib庫有關了。

最終原因分析:

因為通過環境變量設定了xml解析器的實作類,是以不同的xml類庫在建立xml parse時,都會嘗試去載入對應的lib庫。

jetty啟動時,其自身需要解析xml檔案,這時候就出現問題了,jetty預設是沒有xerces xml的解析類的,是以啟動就出錯了。

參照jboss的共享lib的配置,在jetty的ext庫裡添加了xercesimpl-2.9.1.jar,xml-apis-1.3.04.jar,xml-resolver-1.2.jar

因為我使用的是外部ext庫,不想放到jetty軟體的lib庫下,是以需要通過手工指定,在start.ini中添加:

是一個mail郵件發送時發現的問題,從堆棧資訊描述看也很見到,對應的javax.mail.event.transportlistener沒找到

mail的lib庫也是挺讓人糾結的

1. xml一樣有多個lib庫:j2ee.jar,javamail。

2. 但有一點又不同的是j2ee.jar中隻有接口申明沒有對應的實作類

3. 更糾結的是j2ee-1.4新版本和老的javamail版本接口上還存在差異性(這個是别人告訴我的,個人沒仔細驗證)

看到這,各位看官需要多淡定一下,真是很讓人糾結啊

最終原先分析:

原先jboss容器運作沒問題,主要是和其預設的lib庫有關,jboss 4.05在預設的server/default/lib下有個jboss-j2ee.jar,是以即使容器中沒有javamail的包也能正常運作。

換成jetty以後,因預設在jetty下沒有j2ee這個lib庫,是以很悲慘,抛異常了。

1. 沒必要參合j2ee.jar,直接在原先的工程裡添加javamail的依賴,最後在應用中引入javamail的包。

 結尾

在這次的jetty使用過程,還是遇到了比較多的問題,大部分還是在classloader機制上,希望通過這篇文章能對大家有所幫助,少走彎路。

寫的有點小亂,大家将就看一下,如有問題,歡迎拍磚。