tomcat的類(lèi)加載器初步認(rèn)識(shí)


  • tags: tomcat
  • categories:筆記
  • date: 2017-03-09 21:02:08

前幾天初步通過(guò)源代碼看看并且學(xué)習(xí)了JDK的類(lèi)加載器相關(guān)內(nèi)容。其實(shí)在java的生態(tài)系統(tǒng)平臺(tái)內(nèi),類(lèi)加載器是至關(guān)重要的組件吧,因?yàn)樵O(shè)計(jì)到了OOP的對(duì)象抽象class的加載問(wèn)題。若是連OOP的基礎(chǔ)class都加載不了,那也不要說(shuō)再干些其他的事情了。所以從另一方面來(lái)就說(shuō)明了它的重要性,以至于不同的其他軟件基金會(huì)例如apache中tomcat,我們經(jīng)常使用的tomcat貓,就有自己的類(lèi)加載機(jī)制。不過(guò)tomcat的類(lèi)加載器與jdk自己的雙親委托模型的類(lèi)加載可是有不同的,而且tomcat中對(duì)類(lèi)加載的使用也是值得我們?nèi)ド钊肓私饬私獾?。這次,我想從基于我使用了挺久的tomcat貓看看,它的類(lèi)加載器大致是如何工作的,核心的東西主要有那些呢?就來(lái)個(gè)對(duì)貓貓的初步探索吧,應(yīng)該能給自己對(duì)這只貓的理解更細(xì)致吧。


cat.jpg

在開(kāi)始呢,其實(shí)tomcat的源代碼挺多的,內(nèi)部細(xì)節(jié)挺多的,apache力量真是讓人欽佩。造出了很多有意思,有用的輪子。給我們這些用輪子的人,能養(yǎng)家糊口,工作需要,學(xué)習(xí)需要等等。對(duì)于我這個(gè)java方向的碼農(nóng),當(dāng)然大apache啦。所以,就根據(jù)自己的理解和參考一些網(wǎng)上大神的理解自己也倒騰倒騰tomcat的類(lèi)加載器方面的內(nèi)容,也不枉我經(jīng)常使用它。對(duì)他其實(shí)也是一知半解,都是在使用層次上理解它,它的內(nèi)部的東西真是不甚了解,對(duì)我來(lái)說(shuō)挺神秘的。所以,我想慢慢通過(guò)自己對(duì)它內(nèi)部的重要組件的學(xué)習(xí),剖析來(lái)揭開(kāi)它的神秘面紗。↖(▔▽▔)↗ (這里使用的是tomcat7源代碼)

在學(xué)習(xí)tomcat的類(lèi)加載之前,必須要明確一個(gè)概念或者說(shuō)的基本環(huán)境:tomcat服務(wù)器本身和部署在其之上的應(yīng)用之間類(lèi)關(guān)系,tomcat服務(wù)器自身啟動(dòng),也是依賴(lài)與jdk的,也是需要通過(guò)自己的類(lèi)加載加載它所需要的很多類(lèi);在tomcat啟動(dòng)成功之后,我們每次部署的應(yīng)用也是需要加載自己所需要的類(lèi)的,以及每個(gè)web應(yīng)用當(dāng)然都會(huì)加載自己所需要的類(lèi)。所以,不同的類(lèi)加載器,就將這些tomcat自己?jiǎn)?dòng)運(yùn)行需要的類(lèi),單個(gè)應(yīng)用需要的類(lèi),應(yīng)用之間共享的基礎(chǔ)類(lèi)庫(kù)資源進(jìn)行了規(guī)劃與管理。

tomcat的類(lèi)加載器有哪些?

要想知道類(lèi)加載器相關(guān)的內(nèi)容,當(dāng)然要知道除了在jdk基礎(chǔ)上的三種類(lèi)加載(Bootstrap,ext,appclassLoader),它有那些自定義的類(lèi)加載呢?也只有在知道tomcat有那些自己定義的類(lèi)加載的前提下,才能知道它們是如何聯(lián)系的,具體是如何在tomcat運(yùn)行中加載類(lèi)的呢?與jdk的JVM加載類(lèi)過(guò)程有什么不同么?

其實(shí),想要知道tomcat的類(lèi)加載很簡(jiǎn)單,就是調(diào)用getClassLoader和getParent等方法就夠了。寫(xiě)個(gè)簡(jiǎn)單的servlet,然后對(duì)類(lèi)加載器信息進(jìn)行在瀏覽器頁(yè)面輸出即可。具體的例子可以參考下面:(多了寫(xiě)無(wú)關(guān)的格式輸出,為了看起來(lái)更直觀)

/*ClassLoaderServlet.java*/
public class ClassLoaderServlet extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws Exception{

        response.setContentType("text/html");
        response.setCharacterEncoding("utf8");
        PrintWriter out = response.getWriter();
        ClassLoader loader = getClass().getClassLoader();
        String qname = getClass().getName();
        out.println("<!DOCTYPE \">");
        out.println("<HTML>");
        out.println("  <HEAD><TITLE>Tomtcat ClassLoader</TITLE></HEAD>");
        out.println("  <BODY>");
        out.println("當(dāng)前類(lèi):"+qname.substring(qname.lastIndexOf(".")+1)+"的類(lèi)加載器是:"+loader.getClass().getName()+"</br>");
        out.println(loader+"</br>");
        out.println("-----------------"+"</br>");
        while(loader != null){
            ClassLoader tmp = loader;
            loader = loader.getParent();
            if(loader != null){
                out.println(tmp.getClass().getName()+"的父類(lèi)加載器是:"+ loader.getClass().getName()+"</br>");
            }else{
                out.println(tmp.getClass().getName()+"的父類(lèi)加載器是:"+ loader+"</br>");
            }
            out.println("-----------------"+"</br>");
        }
        out.println("<hr>");
        out.println("  </BODY>");
        out.println("</HTML>");
        out.flush();
        out.close();

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        // TODO Auto-generated method stub
        super.doGet(req, resp);
    }

}

在web.xml中進(jìn)行簡(jiǎn)單的servlet配置,通過(guò)瀏覽器訪問(wèn)即可:

    <servlet>
        <servlet-name>ClassLoaderServlet</servlet-name>
        <servlet-class>servlet.classloader.ClassLoaderServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>ClassLoaderServlet</servlet-name>
        <url-pattern>/servlet/ClassLoaderServlet</url-pattern>
    </servlet-mapping>

最后可以得到tomcat的類(lèi)加載信息:可以看到除了jdk基礎(chǔ)的三層類(lèi)加載器結(jié)構(gòu),其實(shí)下面還是有例如StandardClassLoader,WebappClassLoader,CommonClassLoader,ShareClassLoader,ServerClassLoader等自定義類(lèi)加載器的。

當(dāng)前類(lèi):ClassLoaderServlet的類(lèi)加載器是:org.apache.catalina.loader.WebappClassLoader
WebappClassLoader context: /springmvcdemo delegate: false repositories: /WEB-INF/classes/ ----------> Parent Classloader: org.apache.catalina.loader.StandardClassLoader@38d1258b 
-----------------
org.apache.catalina.loader.WebappClassLoader的父類(lèi)加載器是:org.apache.catalina.loader.StandardClassLoader
-----------------
org.apache.catalina.loader.StandardClassLoader的父類(lèi)加載器是:sun.misc.Launcher$AppClassLoader
-----------------
sun.misc.Launcher$AppClassLoader的父類(lèi)加載器是:sun.misc.Launcher$ExtClassLoader
-----------------
sun.misc.Launcher$ExtClassLoader的父類(lèi)加載器是:null
-----------------

## 所以,根據(jù)上述的servlet程序,可以看到tomcat中的類(lèi)加載器的層級(jí)結(jié)構(gòu)如下:
## 但是要注意,這個(gè)類(lèi)結(jié)構(gòu)其實(shí)與tomcat官方文檔的類(lèi)加載結(jié)構(gòu)有些區(qū)別。那具體有什么區(qū)別或者聯(lián)系?
        BootStrapClassLoader
            |
        ExtClassLoader
            |   
        AppClassLoader
            |
        StandardClassLoader
            |
        WebappClassLoader

其實(shí),在tomcat官方文檔中,說(shuō)明了tomcat7的類(lèi)加載結(jié)構(gòu)如下圖:(每個(gè)層次的類(lèi)加載器也有說(shuō)明)
注意每層的類(lèi)加載器與JVM的概念并不是完全的一樣的。但是tomcat所有自定義的類(lèi)加載都是繼承AppClassLoader這個(gè)系統(tǒng)類(lèi)加載器的。

    Bootstrap  --也不完全是JVM的bootstrap
       |
     System    --也不完全是JVM的appClassLoader的概念
       |
     common
    /   \
  Webapp1   Webapp2....

對(duì)于每個(gè)層次的類(lèi)加載是這么說(shuō)明的:(英文翻譯有點(diǎn)菜,肯定有差異啦,建議英語(yǔ)好的可以自己看英文去啦,可以點(diǎn)擊官網(wǎng)查看官方tomcat7類(lèi)加載器說(shuō)明](http://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html))

那CATALINA_HOME和CATALINA_BASE有什么區(qū)別呢?總的來(lái)說(shuō),區(qū)別就是在設(shè)置tomcat多實(shí)例的時(shí)候。在單實(shí)例時(shí)候,也就是我們經(jīng)常使用的狀態(tài),這兩個(gè)變量指向的都是tomcat的安裝目錄(包含完整的bin,conf,webapps,logs,temp,work等等文件夾); 多實(shí)例也就是在一個(gè)主tomcat下(可被公有使用),配置多個(gè)私有tomcat實(shí)例,每個(gè)實(shí)例都包含完整的私有目錄,如conf,webapps,work,logs目錄。總結(jié)來(lái)說(shuō),CATALINA_HOME是安裝目錄,CATALINA_BASE是每個(gè)tomcat實(shí)例的工作根目錄
tomcat單實(shí)例與多實(shí)例實(shí)踐
CATALINA_HOME與CATALINA_BASE

  • Bootstrap:
    這個(gè)類(lèi)加載用于加載JVM運(yùn)行提供的基礎(chǔ)類(lèi)以及拓展路徑($JAVA_HOME/jre/lib/ext)類(lèi)庫(kù)中的類(lèi)。(自己口水話(huà):其實(shí)也就是為tomcat自身服務(wù)器的啟動(dòng)運(yùn)行以及應(yīng)用中用到的一些JDK基礎(chǔ)類(lèi)庫(kù)中的類(lèi)進(jìn)行加載)

  • System:
    這個(gè)類(lèi)加載器通常是用來(lái)初始化那些在CLASSPATH環(huán)境變量路徑上的變量?jī)?nèi)容的,在這類(lèi)庫(kù)路徑上的所有類(lèi)對(duì)于tomcat服務(wù)器本身以及服務(wù)器上部署的web應(yīng)用程序來(lái)說(shuō)都是可見(jiàn)的。然而,對(duì)于標(biāo)準(zhǔn)的tomcat容器啟動(dòng)時(shí)的腳本($CATALINA_HOME/bin/catalina.sh 或者%CATALINA_HOME%\bin\catalina.bat)來(lái)說(shuō),這些啟動(dòng)腳本總是忽略CLASSPATH路徑上的內(nèi)容變量,轉(zhuǎn)而讀取其他路徑上的類(lèi)庫(kù)來(lái)構(gòu)建system類(lèi)加載。

    eg:我們通常如果在tomcat安裝目錄中啟動(dòng)tomcat服務(wù)器,都是運(yùn)行startup.bat或者startup.sh,其實(shí)這些腳本內(nèi)部主要工作也就是調(diào)用catalina.bat來(lái)啟動(dòng)服務(wù)器??纯磗tartup.bat中部分windows腳本,也就是讀取tomcat主目錄,查找catalina.bat腳本,若是存在,則執(zhí)行該腳本啦:

if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
echo The CATALINA_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome

set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"   --設(shè)置執(zhí)行腳本文件路徑
rem Check that target executable exists
if exist "%EXECUTABLE%" goto okExec                 --腳本存在,則調(diào)到執(zhí)行子函數(shù)

然后我們可以看看catalina.bat中對(duì)于CLASSPATH環(huán)境變量的設(shè)置:(截取部分相關(guān)內(nèi)容)

rem Ensure that any user defined CLASSPATH variables are not used on startup,
rem but allow them to be specified in setenv.bat, in rare case when it is needed.
set CLASSPATH=

rem Add on extra jar file to CLASSPATH
rem Note that there are no quotes as we do not want to introduce random
rem quotes into the CLASSPATH
if "%CLASSPATH%" == "" goto emptyClasspath
set "CLASSPATH=%CLASSPATH%;"
:emptyClasspath
set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"

set "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar"
goto juliClasspathDone
:juliClasspathHome
set "CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\bin\tomcat-juli.jar"
:juliClasspathDone

從腳本代碼中,確實(shí)可以看到,CLASSPATH變量在這個(gè)類(lèi)加載階段是要讀取bootstrap.jar和tomcat-juli.jar兩個(gè)類(lèi)庫(kù)文件的。

system類(lèi)加載器的讀取路徑包括以下幾個(gè):

  • $CATALINA_HOME/bin/bootstrap.jar : 這個(gè)類(lèi)庫(kù)包含了在啟動(dòng)tomcat服務(wù)器時(shí),用于初始化服務(wù)器配置的main()方法中所依賴(lài)的所有類(lèi)的實(shí)現(xiàn)。(main方法內(nèi),調(diào)用了bootstrap.init()方法,也就是bootstrap.jar時(shí)tomcat服務(wù)器啟動(dòng)階段的核心類(lèi)庫(kù),不能加載這個(gè)類(lèi)庫(kù)的話(huà),tomcat服務(wù)器啟動(dòng)失敗)

看了看bootstrap.jar中目錄結(jié)構(gòu)如下:

bootstrap.jar
├─loader
│      StandardClassLoader.class
│      StandardClassLoaderMBean.class
│
├─security
│      SecurityClassLoad.class
│
└─startup
        Bootstrap.class  --主要有個(gè)init()方法,用于初始化CatalinaHome,CatalinaBase,配置線(xiàn)程類(lèi)加載器,讀取了catalina.properties文件加載commonLader,catalinaLoader,sharedLoader等類(lèi)加載器。該類(lèi)包含了tomcat服務(wù)器生命周期服務(wù)的所有方法。
        catalina.properties
        CatalinaProperties.class
        ClassLoaderFactory.class
        Tool.class
  • $CATALINA_HOME/bin/tomcat-juli.jar或者$CATALINA_BASE/bin/tomcat-juli.jar: 哎呀,這個(gè)java類(lèi)庫(kù)包,就是用來(lái)強(qiáng)化tomcat日志相關(guān)方面的。不要小看日志,日志可是很重要的東西哦。從上面catalina.bat腳本也看到了CLASSPATH配置了tomcat-juli.jar包。

  • $CATALINA_HOME/bin/commons-daemon.jar: 這個(gè)類(lèi)庫(kù)不在catalina.bat|.sh腳本構(gòu)建的CLASSPATH變量?jī)?nèi),而是與bootstrap.jar類(lèi)庫(kù)引用相關(guān)。在tomcat服務(wù)啟動(dòng)時(shí)相關(guān)守護(hù)線(xiàn)程的管理。

public final class Bootstrap
{

  private static Bootstrap daemon = null;
  private Object catalinaDaemon = null;
  ...
   public static void main(String[] args)
  {
    if (daemon == null)
    {
      Bootstrap bootstrap = new Bootstrap();
      try {
        bootstrap.init();
      } catch (Throwable t) {
        handleThrowable(t);
        t.printStackTrace();
        return;
      }
      daemon = bootstrap;

   } .... 
}
  • Common
    該類(lèi)加載器加載那些可被tomcat服務(wù)器自身和部署的所有web應(yīng)用均可見(jiàn)(可以調(diào)用)的類(lèi)。通常一般不會(huì)修改或者用其他類(lèi)庫(kù)來(lái)替換Common加載的原有類(lèi)庫(kù)。該類(lèi)加載器加載類(lèi)庫(kù)的搜索路徑時(shí)被定義在catalina.properties($CATALINA_BASE/conf/catalina.properties)文件中的common.loader屬性值中。common.loader默認(rèn)的類(lèi)庫(kù)搜索路徑會(huì)按照以下排列順序進(jìn)行加載:
    A。加載$CATALINA_BASE/lib中未被打包的類(lèi)和資源。
    B。加載$CATALINA_BASE/lib中所有jar類(lèi)庫(kù)。
    C。加載$CATALINA_HOME/lib中未被打包的類(lèi)文件與資源。
    D。加載$CATALINA_HOME/lib中所有jar類(lèi)庫(kù)包
    至于那些lib下的每個(gè)jar包的資源有什么作用可以查看官方文檔,有詳細(xì)說(shuō)明。
    我們可以查看catalina.properties文件中common.loader屬性,看看有那些默認(rèn)的配置:
#$CATALINA_BASE/conf/catalina.properties
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
  • WebappX
    這個(gè)類(lèi)加載器被創(chuàng)建是用來(lái)加載tomcat服務(wù)器單實(shí)例上每個(gè)應(yīng)用自己所需要的類(lèi)。(注意每個(gè)實(shí)例之間的類(lèi)默認(rèn)是互不可見(jiàn)的),該加載器加載的類(lèi)庫(kù)不僅包括每個(gè)web應(yīng)用下/WEB-INF/classes目錄下的所有.class類(lèi)和資源文件,還包括web應(yīng)用下/WEB-INF/lib目錄下所有的jar類(lèi)庫(kù)。這兩個(gè)路徑下的類(lèi)字節(jié)碼和jar類(lèi)庫(kù)對(duì)于每個(gè)單應(yīng)用程序都是可見(jiàn)的,但是對(duì)于同個(gè)實(shí)例上其他web應(yīng)用是不可見(jiàn)的。
    通常web應(yīng)用目錄大概如下:(每個(gè)web應(yīng)用編譯后的java文件到classes目錄和lib目錄在WEB-INF同一級(jí)子目錄,不過(guò)classes文件加載類(lèi)在lib之前)
├─common
│  ├─ace
│  ├─css
│  ├─image
│  ├─js
│  └─jsp
├─demo
├─jsp
├─META-INF
└─WEB-INF
    ├─classes
    │  └─demo
    │      └─classloader
    └─lib

在知道了tomcat有那些類(lèi)加載之后,我們下一步,就可以探索它們之間的,自己內(nèi)部的加載器有什么聯(lián)系和它們是如何協(xié)同來(lái)加載class的,與jdk的雙親委托加載有什么不同和相同點(diǎn)?

內(nèi)部類(lèi)加載關(guān)系與加載類(lèi)過(guò)程?

根據(jù)官網(wǎng)上所說(shuō)的,將tomcat類(lèi)加載器級(jí)別籠統(tǒng)的分為Bootstrap,System,Common,WebappX級(jí)別。也是按照父子級(jí)別從左往右形成層級(jí)關(guān)系。那么tomcat服務(wù)器上的每個(gè)web應(yīng)用程序是如何加載一個(gè)類(lèi)呢?還是雙親委托模型么?tomcat程序的加載類(lèi)的過(guò)程與JVM加載類(lèi)的雙親委托機(jī)制是有些差異的,我們就要關(guān)注這個(gè)差異點(diǎn)。

類(lèi)加載路徑順序?

當(dāng)tomcat應(yīng)用中,在處理加載某個(gè)類(lèi)的請(qǐng)求時(shí)候,默認(rèn)情況下,首先處理該請(qǐng)求的是WebappX級(jí)別層次的類(lèi)加載器。在WebappX類(lèi)加載器加載類(lèi)的時(shí)候,首先會(huì)從自己的應(yīng)用的類(lèi)庫(kù)路徑中查找,而不是將該請(qǐng)求委托給自己的父級(jí)類(lèi)加載器來(lái)處理[這里的父子級(jí)別類(lèi)加載器只是限于tomcat自定義的類(lèi)加載]。
但是,在WebappX加載類(lèi)這個(gè)階段,也是有例外的情況。也就是屬于JRE類(lèi)庫(kù)中的基類(lèi)是不能被覆蓋重寫(xiě)的(eg:在web應(yīng)用中定義了java.lang.Object類(lèi),由于是WebappX類(lèi)加載器先加載,那當(dāng)然不能將該自定義同名的class類(lèi)將JRE的java.lang.Object類(lèi)給替換覆蓋了)。
而其他某些類(lèi),例如J2SE1.4后的解析XML組件類(lèi)庫(kù)或者其他非JRE基礎(chǔ)類(lèi)可以被重寫(xiě)(關(guān)于XML Parsers與java JDK之間的實(shí)現(xiàn)封裝關(guān)系可以自己去查看)。
特別的,在web應(yīng)用中任何包含servlet api類(lèi)的jar文件都會(huì)被該類(lèi)加載器顯式的忽略掉,這就意味著不必要將這些類(lèi)型的jars文件放置在web應(yīng)用類(lèi)庫(kù)中。在web應(yīng)用加載某個(gè)類(lèi)的時(shí)候,除了WebappX類(lèi)加載器可以首先嘗試加載該類(lèi),其余的類(lèi)加載器都會(huì)按照J(rèn)VM中的雙親委托模型來(lái)加載該類(lèi)。

因此,根據(jù)web應(yīng)用程序的目錄結(jié)構(gòu),在web應(yīng)用中加載某個(gè)類(lèi)或者資源的時(shí)候,類(lèi)加載器查找類(lèi)將會(huì)按照下面的路徑順序來(lái)查找:(tomcat服務(wù)器正常啟動(dòng)后)

  • JVM內(nèi)bootstrap 基礎(chǔ)類(lèi)庫(kù)(對(duì)應(yīng)JVM中的BootstrapClassLoader,ExtClassLoader)。
  • 每個(gè)web應(yīng)用項(xiàng)目下/WEB-INF/classes目錄下的所有類(lèi) (相當(dāng)于WebappX級(jí)別加載器)
  • 每個(gè)web應(yīng)用項(xiàng)目下/WEB-INF/lib/*.jar所有jar庫(kù)包 (相當(dāng)于WebappX級(jí)別加載器)
  • System 類(lèi)加載器加載的類(lèi)庫(kù)(上面所說(shuō)的)
  • Common 類(lèi)加載加載的類(lèi)庫(kù)(上面所說(shuō)的)

但是,這個(gè)路徑順序也是可以通過(guò)配置修改的,我們可以看到上面ClassLoaderServlet在輸出類(lèi)加載的時(shí)候,注意到輸出WebappClassLoader屬性:

WebappClassLoader context: /springmvcdemo delegate: false repositories: /WEB-INF/classes/ 
----------> Parent Classloader: org.apache.catalina.loader.StandardClassLoader@38d1258b 

注意到,有個(gè)delegate:false字段,就是是否代理的意思??梢钥吹?,在沒(méi)有使用代理情況下,類(lèi)加載器是按照上述路徑查找的。反過(guò)來(lái)想想若是強(qiáng)制顯式的使用代理配置,類(lèi)查找路徑將會(huì)有影響的。在web應(yīng)用中,可以通過(guò)設(shè)置Loader組件配置<Loader delegate="true"/>來(lái)委托查找類(lèi),配置詳情
順序?qū)?huì)修改成下面順序:

  • JVM內(nèi)bootstrap 基礎(chǔ)類(lèi)庫(kù)。
  • System 類(lèi)加載器加載的類(lèi)(上面所說(shuō)的)
  • Common 類(lèi)加載加載的類(lèi)(上面所說(shuō)的)
  • 每個(gè)web應(yīng)用項(xiàng)目下/WEB-INF/lib/*.jar所有jar庫(kù)包 (相當(dāng)于WebappX級(jí)別加載器)
  • 每個(gè)web應(yīng)用項(xiàng)目下/WEB-INF/classes目錄下的所有類(lèi) (相當(dāng)于WebappX級(jí)別加載器)

可以看到完全是與JVM的雙親委托模型加載機(jī)制一樣了,最后才是自定義的WebappX來(lái)加載類(lèi)。

tomcat的web應(yīng)用class加載過(guò)程?

這一點(diǎn),其實(shí)通過(guò)tomcatd的WebappClassLoader.java源代碼可以看出:

    /**
     * Should this class loader delegate to the parent class loader
     * <strong>before</strong> searching its own repositories (i.e. the
     * usual Java2 delegation model)?  If set to <code>false</code>,
     * this class loader will search its own repositories first, and
     * delegate to the parent only if the class or resource is not
     * found locally. Note that the default, <code>false</code>, is
     * the behavior called for by the servlet specification.
     */
    protected boolean delegate = false;

想要具體看看tomcat的web應(yīng)用上class是如何加載,那么可以通過(guò)查看WebappClassLoader.java源代碼來(lái)分析分析了, 加載類(lèi)的loadClass方法如下:

    /**
     * Load the class with the specified name, searching using the following
     * algorithm until it finds and returns the class.  If the class cannot
     * be found, returns <code>ClassNotFoundException</code>.
     * <ul>
     * <li>Call <code>findLoadedClass(String)</code> to check if the
     *     class has already been loaded.  If it has, the same
     *     <code>Class</code> object is returned.</li>
     * <li>If the <code>delegate</code> property is set to <code>true</code>,
     *     call the <code>loadClass()</code> method of the parent class
     *     loader, if any.</li>
     * <li>Call <code>findClass()</code> to find this class in our locally
     *     defined repositories.</li>
     * <li>Call the <code>loadClass()</code> method of our parent
     *     class loader, if any.</li>
     * </ul>
     * If the class was found using the above steps, and the
     * <code>resolve</code> flag is <code>true</code>, this method will then
     * call <code>resolveClass(Class)</code> on the resulting Class object.
     *
     * @param name Name of the class to be loaded
     * @param resolve If <code>true</code> then resolve the class
     *
     * @exception ClassNotFoundException if the class was not found
     */
    @SuppressWarnings("sync-override")
    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (getClassLoadingLockInternal(name)) {

            Class<?> clazz = null;
            //注意,這里是查找的本地tomcat應(yīng)用的查找class緩存
        //調(diào)用的是本類(lèi)中自定義的方法
            // (0) Check our previously loaded local class cache
            clazz = findLoadedClass0(name);     
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
            //調(diào)用的是java.lang.ClassLoader的JVM的查找class緩存
            // (0.1) Check our previously loaded class cache
            clazz = findLoadedClass(name);      
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // (0.2) Try loading the class with the system class loader, to prevent
            //       the webapp from overriding J2SE classes
            try {
                //可以看到這里是因?yàn)閣ebappClassLoader首先加載web應(yīng)用class類(lèi)的緣故,
                //從而會(huì)判斷class是不是JVMSE中的基礎(chǔ)類(lèi)庫(kù)中類(lèi)。防止覆蓋基礎(chǔ)類(lèi)實(shí)現(xiàn)。
                clazz = j2seClassLoader.loadClass(name);  
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }

            boolean delegateLoad = delegate || filter(name);    
           /*先判斷是否設(shè)置了delegate屬性,設(shè)置為true,那么就會(huì)完全按照J(rèn)VM的"雙親委托"機(jī)制流程加載類(lèi)。
            *若是默認(rèn)的話(huà),是先使用WebappClassLoader自己處理加載類(lèi)的。
            *當(dāng)然了,若是委托了,使用雙親委托亦沒(méi)有加載到class實(shí)例,那還是最后使用WebappClassLoader加載。
            */
            // (1) Delegate to our parent if requested
            if (delegateLoad) {                         
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // (2) Search local repositories
            try {
    //若是沒(méi)有委托,則默認(rèn)會(huì)首次使用WebappClassLoader來(lái)加載類(lèi)。通過(guò)自定義findClass定義處理類(lèi)加載規(guī)則。
                clazz = findClass(name);    
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }

//若是WebappClassLoader在/WEB-INF/classes,/WEB-INF/lib下還是查找不到class,
//那么無(wú)條件強(qiáng)制委托給System,Common類(lèi)加載器去查找該類(lèi)。
            // (3) Delegate to parent unconditionally
            if (!delegateLoad) {        
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }

        throw new ClassNotFoundException(name);
    }


###這里注意到上面的j2seClassLoader.loadClass其實(shí)就是調(diào)用JVM中雙親類(lèi)加載機(jī)制來(lái)確保J2SE基礎(chǔ)類(lèi)不被破環(huán),
###與上述的類(lèi)查找步驟第一步bootstrap基礎(chǔ)類(lèi)庫(kù)是想吻合的。
    public WebappClassLoaderBase(ClassLoader parent) { 
        ...
        ClassLoader j = String.class.getClassLoader();  
        if (j == null) {
            j = getSystemClassLoader();   //調(diào)用JVM的java.lang.ClassLoader的方法得到appClassLoader
            while (j.getParent() != null) {
                j = j.getParent();      //得到ExtClassLoader
            }
        }
        this.j2seClassLoader = j;
        ...
    }


哎呀,這個(gè)過(guò)程與JVM的類(lèi)加載器過(guò)程差不多,就不用畫(huà)流程圖了。這里也要知道,在查找某個(gè)class緩存時(shí)候,先是找的tomcat應(yīng)用自己在啟動(dòng)時(shí)候加載的類(lèi)資源緩存,若是查找不到了,才找JVM的class緩存。也要注意某些重要的方法有override重寫(xiě)關(guān)鍵字,對(duì)于我們理解它們關(guān)系還是有幫助的。類(lèi)加載器之間的關(guān)系和各自職責(zé)也差不多了解了,最后,在看看webappClassLoader是如何加載某個(gè)class的吧。具體的細(xì)節(jié),就是在上述代碼最后的findClass方法中。

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {

        // Ask our superclass to locate this class, if possible
        // (throws ClassNotFoundException if it is not found)
        Class<?> clazz = null;
        try {
            if (hasExternalRepositories && searchExternalFirst) {
                try {
                /*又試圖先調(diào)用父類(lèi)URLClassLoader的findClass方法,URLClassLoader確實(shí)有該方法,
                *該方法是用來(lái)通過(guò)URLClassPath查找name對(duì)應(yīng)的符合URL規(guī)范的class類(lèi)路徑,讀取該類(lèi)字節(jié)碼,
                *在調(diào)用defineClass方法定義對(duì)應(yīng)class實(shí)例。(注意super與getParent可是有很大的不同)
                */
                    clazz = super.findClass(name);  
                } catch(ClassNotFoundException cnfe) {
                    // Ignore - will search internal repositories next
                } catch(AccessControlException ace) {
                    log.warn("WebappClassLoaderBase.findClassInternal(" + name
                            + ") security exception: " + ace.getMessage(), ace);
                    throw new ClassNotFoundException(name, ace);
                } catch (RuntimeException e) {
                    if (log.isTraceEnabled())
                        log.trace("      -->RuntimeException Rethrown", e);
                    throw e;
                }
            }
            if ((clazz == null)) {      //父類(lèi)無(wú)法加載該類(lèi)
                try {
                /*加載當(dāng)前web應(yīng)用所有資源路徑下的類(lèi)字節(jié)文件,就是處理ResourceEnty,path,class等等東西
                *最后也都是在找到class二進(jìn)制字節(jié)流后,使用defineClass方法來(lái)定義class實(shí)例。
                */
                    clazz = findClassInternal(name);    
                } catch(ClassNotFoundException cnfe) {
                    if (!hasExternalRepositories || searchExternalFirst) {
                        throw cnfe;
                    }
                } catch(AccessControlException ace) {
                    log.warn("WebappClassLoaderBase.findClassInternal(" + name
                            + ") security exception: " + ace.getMessage(), ace);
                    throw new ClassNotFoundException(name, ace);
                } catch (RuntimeException e) {
                    if (log.isTraceEnabled())
                        log.trace("      -->RuntimeException Rethrown", e);
                    throw e;
                }
            }
            if ((clazz == null) && hasExternalRepositories && !searchExternalFirst) {
                try {
                    clazz = super.findClass(name);
                } catch(AccessControlException ace) {
                    log.warn("WebappClassLoaderBase.findClassInternal(" + name
                            + ") security exception: " + ace.getMessage(), ace);
                    throw new ClassNotFoundException(name, ace);
                } catch (RuntimeException e) {
                    if (log.isTraceEnabled())
                        log.trace("      -->RuntimeException Rethrown", e);
                    throw e;
                }
            }
            if (clazz == null) {
                if (log.isDebugEnabled())
                    log.debug("    --> Returning ClassNotFoundException");
                throw new ClassNotFoundException(name);
            }
        } catch (ClassNotFoundException e) {
            if (log.isTraceEnabled())
                log.trace("    --> Passing on ClassNotFoundException");
            throw e;
        }
        return (clazz);

    }

其實(shí),總的來(lái)說(shuō)呢,tomcat在啟動(dòng)時(shí)候,就會(huì)大致創(chuàng)建以下幾種類(lèi)加載器:

  • Bootstrap類(lèi)加載器
     加載JVM啟動(dòng)所需的類(lèi),以及標(biāo)準(zhǔn)擴(kuò)展類(lèi)(位于jre/lib/ext下)
  • System tomcat系統(tǒng)類(lèi)加載器
    加載tomcat啟動(dòng)的類(lèi),比如bootstrap.jar,tomcat-guli.jar。通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下。
  • Common tomcat服務(wù)通用類(lèi)加載器
    加載tomcat自身使用以及部署在其上面的應(yīng)用程序通用的一些類(lèi),加載路徑可以在catalina.properties中common.loader屬性配置。
  • WebappClassLoader tomcat應(yīng)用類(lèi)加載器
     每個(gè)應(yīng)用在部署后,都會(huì)創(chuàng)建一個(gè)唯一的類(lèi)加載器。該類(lèi)加載器會(huì)加載位于 WEB-INF/lib下的jar文件中的class 和 WEB-INF/classes下的class文件。

當(dāng)在應(yīng)用程序中,需要使用某個(gè)類(lèi)的時(shí)候,tomcat的類(lèi)加載器加載類(lèi)的過(guò)程如下:(在默認(rèn)未設(shè)置委托情況下)

Bootstrap(包含JVM的bootstrapClassLoader,extClassLoader加載的所有基礎(chǔ)的,依賴(lài)的類(lèi)) 
--->/WEB-INF/Classes/*.class
--->/WEB-INF/lib/*.jar 
---> System tomcat(與JVM中的System類(lèi)加載器appClassLoader不同)加載的相關(guān)類(lèi)集合 (%CATALINA_HOME%\bin\catalina.bat)
---> Common加載的相關(guān)類(lèi)集合(%CATALINA_HOME%\conf\catalina.properties)

不同web應(yīng)用間的類(lèi)共享與隔離?

在講完了tomcat的類(lèi)加載器相關(guān)知識(shí),就來(lái)說(shuō)說(shuō)如何利用類(lèi)加載器的加載機(jī)制以及查找類(lèi)路徑特點(diǎn),如何將不同的類(lèi)在不同的應(yīng)用程序之間共享或者隔離?。
其實(shí)隔離的話(huà),不同的tomcat上應(yīng)用的類(lèi)在編譯部署之后都會(huì)在/WEB-INF/classes/目錄下,和/WEB-INF/lib/目錄下的jar庫(kù)一樣,兩者在天生設(shè)計(jì)上就是屬于每個(gè)單個(gè)應(yīng)用自己可見(jiàn),其他應(yīng)用不可見(jiàn)的。

除了這兩個(gè)目錄下處于隔離的類(lèi)庫(kù),我們主要還是要看看如何在同一個(gè)tomcat實(shí)例上,不同的應(yīng)用如何共享一些公共的類(lèi)庫(kù)?
如何讓tomcat多實(shí)例之間使用共享的類(lèi)庫(kù)?。主要就是看看catalina.properties文件,該文件位于$CATALINA_HOME|BASE/conf。catalina.properties配置文件里面對(duì)于類(lèi)庫(kù)的定義細(xì)節(jié)。

#
#
# List of comma-separated paths defining the contents of the "common"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank,the JVM system loader will be used as Catalina's "common"
# loader.
# Examples:
#     "foo": Add this folder as a class repository
#     "foo/*.jar": Add all the JARs of the specified folder as class
#                  repositories
#     "foo/bar.jar": Add bar.jar as a class repository
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar

#
# List of comma-separated paths defining the contents of the "server"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank, the "common" loader will be used as Catalina's "server"
# loader.
server.loader=

#
# List of comma-separated paths defining the contents of the "shared"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_BASE path or absolute. If left as blank,
# the "common" loader will be used as Catalina's "shared" loader.
# Please note that for single jars, e.g. bar.jar, you need the URL form
# starting with file:.
shared.loader=

可以看見(jiàn),有三種類(lèi)加載器可以設(shè)置,默認(rèn)三者配置都是相同的,即都與common.loader的key值相同。關(guān)于三者具體代表什么,可以參考此篇文章Tomcat ClassLoader機(jī)制介紹,
common.loader加載器是server.loader,shared.loader兩個(gè)類(lèi)加載器的父類(lèi)加載器,可以通過(guò)$CATALINA_HOME/bin/bootstrap.jar中可以看出:

public final class Bootstrap
{
  ...
  protected ClassLoader commonLoader = null;
  protected ClassLoader catalinaLoader = null;
  protected ClassLoader sharedLoader = null;

  private void initClassLoaders()
  {
    try
    {
      this.commonLoader = createClassLoader("common", null);
      if (this.commonLoader == null)
      {
        this.commonLoader = getClass().getClassLoader();
      }
      this.catalinaLoader = createClassLoader("server", this.commonLoader);
      this.sharedLoader = createClassLoader("shared", this.commonLoader);
    } catch (Throwable t) {
      handleThrowable(t);
      log.error("Class loader creation threw exception", t);
      System.exit(1);
    }
    ...

  }

    private ClassLoader createClassLoader(String name, ClassLoader parent)
    throws Exception
  {
   ...

   }

 ...
}

那這三個(gè)類(lèi)加載器對(duì)我們應(yīng)用之間的類(lèi)庫(kù)之間共享有什么作用?又該如何使用呢?

通常來(lái)說(shuō),common.loaderserver.loader都是tomcat服務(wù)器自身級(jí)別上面的類(lèi)庫(kù)設(shè)置,我們?cè)谌粘i_(kāi)發(fā)中使用不是太多。若是我們?nèi)我庑薷倪@兩個(gè)文件,對(duì)于不同實(shí)例或者同實(shí)例不同應(yīng)用程序之間都會(huì)有或多或少的影響,如版本沖突,加載不到類(lèi)等。其實(shí)關(guān)于多實(shí)例的這三個(gè)類(lèi)加載器的使用,自己也還沒(méi)有實(shí)踐過(guò),也不能妄下定論。日后經(jīng)過(guò)實(shí)踐之后,再來(lái)總結(jié)和修改。但是,單實(shí)例tomcat上多項(xiàng)目共享類(lèi)庫(kù)倒是可以通過(guò)shared.loader來(lái)配置,也有不少好處。

若是我們?cè)?strong>單個(gè)tomcat上(catalina_home與catalina_base相同),部署著多個(gè)web項(xiàng)目,我們想設(shè)置一些對(duì)于所有web項(xiàng)目都可見(jiàn),可訪問(wèn),即共享。我們可以catalina.properties的shared.loader配置:

shared.loader=shared.loader=${catalina.base}/shared/lib,${catalina.base}/shared/lib/A.jar,b.jar,*.jar...

這就需要我們?cè)谠?catalina_home|base目錄下創(chuàng)建shared目錄或者是shared/lib目錄,并將所有想要共享的jar庫(kù)包,添加到該目錄下。那么每個(gè)應(yīng)用都能加載獲取到該位置的class。在類(lèi)加載器:common class loader ---> server class loader ---> shared class loader過(guò)程,最后可以獲取到指定的共享類(lèi)。這個(gè)路徑既可以是相對(duì)路徑,也可以是絕對(duì)路徑,只要路徑中URL指向的jar庫(kù)能獲取到class字節(jié)碼即可。(其實(shí)也可以在common.loader上配置,但是在common上配置,感覺(jué)侵入式的力度大了些。<( ̄︶ ̄)> )

共享類(lèi)庫(kù)的好處:

  • 可以避免各個(gè)web項(xiàng)目之間重復(fù)加載相同的jar包,對(duì)JVM中存放class信息的內(nèi)存區(qū)壓力增大。這些沒(méi)必要的開(kāi)銷(xiāo)可以減少。
  • 也可以提高tomcat的啟動(dòng)速度,因?yàn)闇p少每個(gè)web項(xiàng)目重復(fù)加載jar的時(shí)間,tomcat的reload速度得到提升。
    ....

好了,自己對(duì)tomcat的類(lèi)加載器的理解寫(xiě)下來(lái),但是自己對(duì)tomcat的理解還不夠深,理解會(huì)有偏差。該片文章主要用于自己記錄筆記并且供日后修改。僅供參考。

參考: Class Loader HOW-TO - tomcat 7 Tomcat ClassLoader機(jī)制介紹

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容