Tomcat源碼分析 -- Tomcat類加載器

本章結(jié)構(gòu)如下:

  • 前言
  • Java類加載機(jī)制
  • tomcat類加載器
  • tomcat類加載器源碼分析

一、前言

下載tomcat解壓后,可以在webapps目錄下看到幾個(gè)文件夾(這些都是web應(yīng)用),webapps對(duì)應(yīng)到tomcat容器中的Host,里面的文件夾則對(duì)應(yīng)到Context。tomcat啟動(dòng)后,webapps下的所有web應(yīng)用都可以提供服務(wù)。

那么就有一個(gè)問(wèn)題,假如webapps下有兩個(gè)應(yīng)用app1和app2,它們有各自獨(dú)立依賴的jar包,又有共同依賴的jar包,這些相同的jar包有些版本相同,有些又不相同,這種情況下,tomcat是如何加載這些jar包的呢?

帶著這個(gè)疑問(wèn),一步步來(lái)分析tomcat的類加載機(jī)制吧。

二、Java類加載機(jī)制

在這之前,當(dāng)然要先了解一下java中類加載時(shí)怎樣的,畢竟tomcat是用java寫的,它的加載機(jī)制也是基于java的類加載機(jī)制。

2.1、類加載器

1.什么是類加載器?

虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類加載階段中的“通過(guò)一個(gè)類的全限定名來(lái)獲取描述此類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到Java虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為“類加載器”。

2.如何判斷兩個(gè)類是否相等?

類加載器用于實(shí)現(xiàn)類的加載動(dòng)作。對(duì)于任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身共同確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類,都擁有一個(gè)獨(dú)立的類名稱空間。也就是說(shuō):比較兩個(gè)類是否“相等”,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義。否則,即使這兩個(gè)類來(lái)源于同一個(gè)Class文件,被同一個(gè)虛擬機(jī)加載,只要加載它們的類加載器不同,那這兩個(gè)類就必定不相等。

2.2、雙親委派模型

Java 提供三種類型的系統(tǒng)類加載器:

  1. 啟動(dòng)類加載器(Bootstrap ClassLoader):由C++語(yǔ)言實(shí)現(xiàn),屬于JVM的一部分,其作用是加載 <JAVA_HOME>\lib 目錄中的文件,或者被-Xbootclasspath參數(shù)所指定的路徑中的文件,并且該類加載器只加載特定名稱的文件(如 rt.jar),而不是該目錄下所有的文件。啟動(dòng)類加載器無(wú)法被Java程序直接引用。
  2. 擴(kuò)展類加載器(Extension ClassLoader):由sun.misc.Launcher.ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫(kù),開(kāi)發(fā)者可以直接使用擴(kuò)展類加載器。
  3. 應(yīng)用程序類加載器(Application ClassLoader):也稱系統(tǒng)類加載器,由sun.misc.Launcher.AppClassLoader實(shí)現(xiàn)。負(fù)責(zé)加載用戶類路徑(Class Path)上所指定的類庫(kù),開(kāi)發(fā)者可以直接使用這個(gè)類加載器,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。

應(yīng)用程序都是由這3種類加載器互相配合進(jìn)行加載的,如果有必要,還可以加入自己定義的類加載器。加載流程如下圖所示:

用一段代碼測(cè)試一下:

 public static void main(String[] args) {
        ClassLoader loader = Xxx.class.getClassLoader();
        while (loader!=null){
            System.out.println(loader);
            loader = loader.getParent();
        }
    }

結(jié)果:

從結(jié)果我們可以看出,默認(rèn)情況下,用戶自定義的類使用 AppClassLoader 加載,AppClassLoader 的父加載器為 ExtClassLoader,但是 ExtClassLoader 的父加載器卻顯示為空,這是什么原因呢?究其緣由,啟動(dòng)類加載器屬于 JVM 的一部分,它不是由 Java 語(yǔ)言實(shí)現(xiàn)的,在 Java 中無(wú)法直接引用,所以才返回空。

java這種類加載層級(jí)稱為雙親委派模型。它的工作過(guò)程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒(méi)有找到所需的類)時(shí),子加載器才會(huì)嘗試自己去加載。

為什么要這樣呢?

都知道java.lang.Object是java中所有類的父類,它存放在rt.jar之中,按照雙親委派模型,無(wú)論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。試想,如果沒(méi)有使用雙親委派模型,由各個(gè)類加載器自行去加載,顯然,這就存在很大風(fēng)險(xiǎn),用戶完全可以惡意編寫一個(gè)java.lang.Object類,然后放到ClassPath下,那系統(tǒng)就會(huì)出現(xiàn)多個(gè)Object類,Java類型體系中最基礎(chǔ)的行為也就無(wú)法保證,應(yīng)用程序也將會(huì)變得一片混亂。

2.3、簡(jiǎn)單看下源碼

protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
    synchronized(this.getClassLoadingLock(var1)) {
        //首先, 檢查請(qǐng)求的類是否已經(jīng)被加載過(guò)了  
        Class var4 = this.findLoadedClass(var1);
        if(var4 == null) {
            long var5 = System.nanoTime();

            try {
                if(this.parent != null) {
                    var4 = this.parent.loadClass(var1, false);
                } else {
                    var4 = this.findBootstrapClassOrNull(var1);
                }
            } catch (ClassNotFoundException var10) {
                //如果父類加載器拋出ClassNotFoundException 
                //說(shuō)明父類加載器無(wú)法完成加載請(qǐng)求  
                ;
            }

            if(var4 == null) {
                long var7 = System.nanoTime();
                //在父類加載器無(wú)法加載的時(shí)候  
                //再調(diào)用本身的findClass方法來(lái)進(jìn)行類加載  
                var4 = this.findClass(var1);
                PerfCounter.getParentDelegationTime().addTime(var7 - var5);
                PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);
                PerfCounter.getFindClasses().increment();
            }
        }

        if(var2) {
            this.resolveClass(var4);
        }

        return var4;
    }
}

從源碼可以看出,ExtClassLoader 和 AppClassLoader都繼承自 ClassLoader 類,ClassLoader 類中通過(guò) loadClass 方法來(lái)實(shí)現(xiàn)雙親委派機(jī)制。整個(gè)類的加載過(guò)程可分為如下三步:

  1. 查找對(duì)應(yīng)的類是否已經(jīng)加載。
  2. 若未加載,則判斷當(dāng)前類加載器的父加載器是否為空,不為空則委托給父類去加載,否則調(diào)用啟動(dòng)類加載器加載(findBootstrapClassOrNull 再往下會(huì)調(diào)用一個(gè) native 方法)。
  3. 若第二步加載失敗,說(shuō)明父類加載器無(wú)法完成加載請(qǐng)求 ,則調(diào)用當(dāng)前類加載器加載。

詳細(xì)可以參考這篇博文:

Java 類加載機(jī)制詳解

2.4、打破雙親委派模型(來(lái)自《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐》)

雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型,而是Java設(shè)計(jì)者推薦給開(kāi)發(fā)者的類加載器實(shí)現(xiàn)方式。

它很好地解決了各個(gè)類加載器的基礎(chǔ)類的統(tǒng)一問(wèn)題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類之所以稱為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸挥脩舸a調(diào)用的API,但世事往往沒(méi)有絕對(duì)的完美,如果基礎(chǔ)類又要調(diào)用回用戶的代碼,那該怎么辦?

這并非是不可能的事情,一個(gè)典型的例子便是JNDI服務(wù),它的代碼由啟動(dòng)類加載器去加載(在JDK1.3時(shí)放進(jìn)rt.jar),但JNDI的目的就是對(duì)資源進(jìn)行集中管理和查找,它需要調(diào)用獨(dú)立廠商實(shí)現(xiàn)部部署在應(yīng)用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼,但啟動(dòng)類加載器不可能“認(rèn)識(shí)”之些代碼,該怎么辦?

Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類加載器(Thread Context ClassLoader)。這個(gè)類加載器可以通過(guò)java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置,它將會(huì)從父線程中繼承一個(gè),如果在應(yīng)用程序的全局范圍內(nèi)都沒(méi)有設(shè)置過(guò)的話,那這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器(Application ClassLoader)。

有了線程上下文類加載器,就可以做一些“舞弊”的事情了,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請(qǐng)求子類加載器去完成類加載的動(dòng)作,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來(lái)逆向使用類加載器,實(shí)際上已經(jīng)違背了雙親委派模型的一般性原則。

還有“被破壞”是由于用戶對(duì)程序的動(dòng)態(tài)性的追求導(dǎo)致的,例如OSGi的出現(xiàn)。在OSGi環(huán)境下,類加載器不再是雙親委派模型中的樹(shù)狀結(jié)構(gòu),而是進(jìn)一步發(fā)展為網(wǎng)狀結(jié)構(gòu)。

當(dāng)收到類加載請(qǐng)求時(shí),OSGi將按照下面的順序進(jìn)行類搜索:
1)將以java.*開(kāi)頭的類委派給父類加載器加載。
2)否則,將委派列表名單內(nèi)的類委派給父類加載器加載。
3)否則,將Import列表中的類委派給Export這個(gè)類的Bundle的類加載器加載。
4)否則,查找當(dāng)前Bundle的Class Path,使用自己的類加載器加載。
5)否則,查找類是否在自己的Fragment Bundle中,如果在,則委派給Fragment Bundle的類加載器加載。
6)否則,查找Dynamic Import列表的Bundle,委派給對(duì)應(yīng)Bundle的類加載器加載。
7)否則,類查找失敗。

三、tomcat類加載器

了解了java的雙親委派模型,現(xiàn)在回到正題上,tomcat的類加載器是怎么樣的?

3.1、Web容器應(yīng)該具備的特性

不難想象,一個(gè)功能健全的Web容器,它的類加載器必然有多個(gè),因?yàn)樗鼞?yīng)該具備如下特性:

  • 隔離性:部署在同一個(gè)Web容器上的兩個(gè)Web應(yīng)用程序所使用的Java類庫(kù)可以實(shí)現(xiàn)相互隔離。設(shè)想一下,兩個(gè)Web應(yīng)用,一個(gè)使用了Spring2.5,另一個(gè)使用了教新的4.0,應(yīng)用服務(wù)器使用一個(gè)類加載器,Web應(yīng)用將會(huì)因?yàn)閖ar包覆蓋而無(wú)法啟動(dòng)。
  • 靈活性:Web應(yīng)用之間的類加載器相互獨(dú)立,那么就能針對(duì)一個(gè)Web應(yīng)用進(jìn)行重新部署,此時(shí)Web應(yīng)用的類加載器會(huì)被重建,而且不會(huì)影響其他的Web應(yīng)用。如果采用一個(gè)類加載器,類之間的依賴是雜亂復(fù)雜的,無(wú)法完全移出某個(gè)應(yīng)用的類。
  • 性能:部署在同一個(gè)Web容器上的兩個(gè)Web應(yīng)用程序所使用的Java類庫(kù)可以互相共享。這個(gè)需求也很常見(jiàn),例如,用戶可能有10個(gè)使用Spring組織的應(yīng)用程序部署在同一臺(tái)服務(wù)器上,如果把10份Spring分別存放在各個(gè)應(yīng)用程序的隔離目錄中,將會(huì)是很大的資源浪費(fèi)——這主要倒不是浪費(fèi)磁盤空間的問(wèn)題,而是指類庫(kù)在使用時(shí)都要被加載到Web容器的內(nèi)存,如果類庫(kù)不能共享,虛擬機(jī)的方法區(qū)就會(huì)很容易出現(xiàn)過(guò)度膨脹的風(fēng)險(xiǎn)。
  • ...

3.2、tomcat類加載器結(jié)構(gòu)

了解了一款Web容器應(yīng)該具備的特性,明白了Web容器的類加載器有多個(gè),再來(lái)看tomcat的類加載器結(jié)構(gòu)。

首先上張圖,整體看下tomcat的類加載器:

可以看到在原先的java類加載器基礎(chǔ)上,tomcat新增了幾個(gè)類加載器,包括3個(gè)基礎(chǔ)類加載器和每個(gè)Web應(yīng)用的類加載器,其中3個(gè)基礎(chǔ)類加載器可在conf/catalina.properties中配置,具體介紹下:

  • Common:以應(yīng)用類加載器為父類,是tomcat頂層的公用類加載器,其路徑由conf/catalina.properties中的common.loader指定,默認(rèn)指向${catalina.home}/lib下的包。
  • Catalina:以Common類加載器為父類,是用于加載Tomcat應(yīng)用服務(wù)器的類加載器,其路徑由server.loader指定,默認(rèn)為空,此時(shí)tomcat使用Common類加載器加載應(yīng)用服務(wù)器。
  • Shared:以Common類加載器為父類,是所有Web應(yīng)用的父類加載器,其路徑由shared.loader指定,默認(rèn)為空,此時(shí)tomcat使用Common類加載器作為Web應(yīng)用的父加載器。
  • Web應(yīng)用:以Shared類加載器為父類,加載/WEB-INF/classes目錄下的未壓縮的Class和資源文件以及/WEB-INF/lib目錄下的jar包,該類加載器只對(duì)當(dāng)前Web應(yīng)用可見(jiàn),對(duì)其他Web應(yīng)用均不可見(jiàn)。

默認(rèn)情況下,Common、Catalina、Shared類加載器是同一個(gè),但可以配置3個(gè)不同的類加載器,使他們各司其職。

首先,Common類加載器復(fù)雜加載Tomcat應(yīng)用服務(wù)器內(nèi)部和Web應(yīng)用均可見(jiàn)的類,如Servlet規(guī)范相關(guān)包和一些通用工具包。

其次,Catalina類加載器負(fù)責(zé)只有Tomcat應(yīng)用服務(wù)器內(nèi)部可見(jiàn)的類,這些類對(duì)Web應(yīng)用不可見(jiàn)。比如,想實(shí)現(xiàn)自己的會(huì)話存儲(chǔ)方案,而且該方案依賴了一些第三方包,當(dāng)然是不希望這些包對(duì)Web應(yīng)用可見(jiàn),這時(shí)可以配置server.load,創(chuàng)建獨(dú)立的Catalina類加載器。

再次,Shared類復(fù)雜加載Web應(yīng)用共享類,這些類tomcat服務(wù)器不會(huì)依賴。

相信看到這,引言中的疑問(wèn)已經(jīng)解開(kāi)了吧。

那還有一個(gè)問(wèn)題,如果有10個(gè)Web應(yīng)用程序都是用Spring來(lái)進(jìn)行組織和管理的話,可以把Spring放到Common或Shared目錄下讓這些程序共享。Spring要對(duì)用戶程序的類進(jìn)行管理,自然要能訪問(wèn)到用戶程序的類,而用戶的程序顯然是放在/WebApp/WEB-INF目錄中的,那么被CommonClassLoader或SharedClassLoader加載的Spring如何訪問(wèn)并不在其加載范圍內(nèi)的用戶程序呢?

如果按主流的雙親委派機(jī)制,顯然無(wú)法做到讓父類加載器加載的類去訪問(wèn)子類加載器加載的類,但使用線程上下文加載器,可以讓父類加載器請(qǐng)求子類加載器去完成類加載的動(dòng)作。spring加載類所用的Classloader是通過(guò)Thread.currentThread().getContextClassLoader()來(lái)獲取的,而當(dāng)線程創(chuàng)建時(shí)會(huì)默認(rèn)setContextClassLoader(AppClassLoader),即線程上下文類加載器被設(shè)置為AppClassLoader,spring中始終可以獲取到這個(gè)AppClassLoader(在Tomcat里就是WebAppClassLoader)子類加載器來(lái)加載bean,以后任何一個(gè)線程都可以通過(guò)getContextClassLoader()獲取到WebAppClassLoader來(lái)getbean了。

接下來(lái),看一看源碼。

四、tomcat類加載器源碼分析

1.Common/Catalina/Shared ClassLoader的創(chuàng)建

首先先看下tomcat的類加載器繼承結(jié)構(gòu):

相信看到這會(huì)很疑惑,這和上面介紹的tomcat類加載器結(jié)構(gòu)不一樣啊。

是這樣的,雙親委派模型本不是通過(guò)繼承實(shí)現(xiàn)的,而是組合,所以AppClassLoader沒(méi)有繼承自ExtClassLoader,WebappClassLoader也沒(méi)有繼承自AppClassLoader。至于Common ClassLoader,Shared ClassLoader,Catalina ClassLoader則是在啟動(dòng)時(shí)初始化的三個(gè)不同名字的URLClassLoader。

來(lái)到BootStrap中看一下:

public static void main(String args[]) {
    if (daemon == null) {
        // Don't set daemon until init() has completed
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.init();
        } catch (Throwable t) {
            handleThrowable(t);
            t.printStackTrace();
            return;
        }
        daemon = bootstrap;
    } else {
        // When running as a service the call to stop will be on a new
        // thread so make sure the correct class loader is used to prevent
        // a range of class not found exceptions.
        Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
    }
    
    ...
}

先判斷Bootstrap是否為null,不為null,直接將Catalina ClassLoader設(shè)置到當(dāng)前線程,用于加載服務(wù)器相關(guān)類,為null則進(jìn)入bootstrap的init方法。

public void init() throws Exception {

    initClassLoaders();

    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);
    
    ...
}

init方法會(huì)調(diào)用initClassLoaders,同樣也會(huì)將Catalina ClassLoader設(shè)置到當(dāng)前線程設(shè)置到當(dāng)前線程,進(jìn)入initClassLoaders來(lái)看看。

private void initClassLoaders() {
    try {
        commonLoader = createClassLoader("common", null);
        if( commonLoader == null ) {
            // no config file, default to this loader - we might be in a 'single' env.
            commonLoader=this.getClass().getClassLoader();
        }
        catalinaLoader = createClassLoader("server", commonLoader);
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}

應(yīng)該就很明白了,會(huì)創(chuàng)建三個(gè)ClassLoader,CommClassLoader,Catalina ClassLoader,SharedClassLoader,正好對(duì)應(yīng)前面介紹的三個(gè)基礎(chǔ)類加載器。

再進(jìn)入createClassLoader可以看到這三個(gè)基礎(chǔ)類加載器所加載的資源剛好對(duì)應(yīng)conf/catalina.properties中的common.loader,server.loader,shared.loader:

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

    String value = CatalinaProperties.getProperty(name + ".loader");
    if ((value == null) || (value.equals("")))
        return parent;

    value = replace(value);

    List<Repository> repositories = new ArrayList<>();

    String[] repositoryPaths = getPaths(value);

    for (String repository : repositoryPaths) {
        // Check for a JAR URL repository
        try {
            @SuppressWarnings("unused")
            URL url = new URL(repository);
            repositories.add(
                    new Repository(repository, RepositoryType.URL));
            continue;
        } catch (MalformedURLException e) {
            // Ignore
        }

        // Local repository
        if (repository.endsWith("*.jar")) {
            repository = repository.substring
                (0, repository.length() - "*.jar".length());
            repositories.add(
                    new Repository(repository, RepositoryType.GLOB));
        } else if (repository.endsWith(".jar")) {
            repositories.add(
                    new Repository(repository, RepositoryType.JAR));
        } else {
            repositories.add(
                    new Repository(repository, RepositoryType.DIR));
        }
    }

    return ClassLoaderFactory.createClassLoader(repositories, parent);
}

2.Common/Catalina/Shared ClassLoader的層次構(gòu)建

Common/Catalina/Shared ClassLoader的創(chuàng)建好了,肯定是要被使用的,是在哪里使用的呢?它們之間同Webapp ClassLoader又是怎么聯(lián)系起來(lái)的?

既然sharedClassLoader被傳入到Catalina中,就來(lái)看它的getParentClassLoader調(diào)用棧。

經(jīng)過(guò)層層調(diào)用,來(lái)帶StandardContext的startInternal方法,這個(gè)方法很長(zhǎng)很復(fù)雜,就不全貼出來(lái),里面有這樣一段:

 if (getLoader() == null) {
    WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
    webappLoader.setDelegate(getDelegate());
    setLoader(webappLoader);
}

它會(huì)創(chuàng)建WebappLoader對(duì)象,并通過(guò)setLoader(webappLoader)賦值到一個(gè)實(shí)例變量中,然后會(huì)調(diào)用WebappLoader的start方法:

...
if (ok) {
    // Start our subordinate components, if any
    Loader loader = getLoader();
    if (loader instanceof Lifecycle) {
        ((Lifecycle) loader).start();
    }
    ...
}
...

這里關(guān)系到tomcat的生命周期機(jī)制,先不糾結(jié),直接找到start方法,start方法是在父類中,最后要調(diào)回到WebappLoader中的startInternal方法。

該方法中有這樣一段:

...
classLoader = createClassLoader();
classLoader.setResources(context.getResources());
classLoader.setDelegate(this.delegate);
...

進(jìn)入createClassLoader方法:

private WebappClassLoaderBase createClassLoader()
        throws Exception {

    // private String loaderClass = ParallelWebappClassLoader.class.getName();
    Class<?> clazz = Class.forName(loaderClass);
    WebappClassLoaderBase classLoader = null;

    if (parentClassLoader == null) {
        parentClassLoader = context.getParentClassLoader();
    }
    Class<?>[] argTypes = { ClassLoader.class };
    Object[] args = { parentClassLoader };
    Constructor<?> constr = clazz.getConstructor(argTypes);
    classLoader = (WebappClassLoaderBase) constr.newInstance(args);

    return classLoader;
}

該方法會(huì)實(shí)例化一個(gè)ParallelWebappClassLoader實(shí)例,并且傳遞了sharedLoader作為其父親加載器。

代碼閱讀到這里,已經(jīng)基本清楚了Tomcat中ClassLoader的總體結(jié)構(gòu),總結(jié)如下: 在Tomcat存在common、cataina、shared三個(gè)公共的classloader,默認(rèn)情況下,這三個(gè)classloader其實(shí)是同一個(gè),都是common classloader,而針對(duì)每個(gè)webapp,也就是context(對(duì)應(yīng)代碼中的StandardContext類),都有自己的WebappClassLoader實(shí)例來(lái)加載每個(gè)應(yīng)用自己的類,該類加載實(shí)例的parent即是Shared ClassLoader。

這樣前面關(guān)于tomcat的類加載層次應(yīng)該就清楚起來(lái)了。

3.tomcat類加載器的加載過(guò)程

前面介紹了tomcat類加載器的創(chuàng)建及層次,下面進(jìn)入本篇最后一點(diǎn)內(nèi)容,這些類加載器是怎樣加載類的呢?

所以重點(diǎn)看ParallelWebappClassLoader(看名字就是并行的WebappClassLoader,具體的差異就不做研究了)。

ParallelWebappClassLoader的loadClass是在其父類WebappClassLoaderBase中實(shí)現(xiàn)的:

第一步:

首先調(diào)用findLoaderClass0() 方法檢查WebappClassLoader中是否加載過(guò)此類。

WebappClassLoader 加載過(guò)的類都存放在 resourceEntries 緩存中。protected final Map<String, ResourceEntry> resourceEntries = new ConcurrentHashMap<>();

第二步:

如果第一步?jīng)]有找到,則繼續(xù)檢查JVM虛擬機(jī)中是否加載過(guò)該類。
調(diào)用ClassLoader的findLoadedClass()方法檢查。

第三步:

如果前兩步都沒(méi)有找到,則使用系統(tǒng)類加載該類(也就是當(dāng)前JVM的ClassPath)。為了防止覆蓋基礎(chǔ)類實(shí)現(xiàn),這里會(huì)判斷class是不是JVMSE中的基礎(chǔ)類庫(kù)中類。

protected ClassLoader getJavaseClassLoader() {
    return javaseClassLoader;
}

第四步:

先判斷是否設(shè)置了delegate屬性,設(shè)置為true,那么就會(huì)完全按照J(rèn)VM的"雙親委托"機(jī)制流程加載類。

若是默認(rèn)的話,是先使用WebappClassLoader自己處理加載類的。當(dāng)然,若是委托了,使用雙親委托亦沒(méi)有加載到class實(shí)例,那還是最后使用WebappClassLoader加載。

第五步:

若是沒(méi)有委托,則默認(rèn)會(huì)首次使用WebappClassLoader來(lái)加載類。通過(guò)自定義findClass定義處理類加載規(guī)則。

findClass()會(huì)去Web-INF/classes 目錄下查找類。

去閱讀里面的代碼,里面有這樣一個(gè)方法:

 @Override
public WebResource getClassLoaderResource(String path) {
    return getResource("/WEB-INF/classes" + path, true, true);
}

第六步:

若是WebappClassLoader在/WEB-INF/classes、/WEB-INF/lib下還是查找不到class,那么無(wú)條件強(qiáng)制委托給System、Common類加載器去查找該類。

最后借tomcat官網(wǎng)上的話總結(jié)一下:

Web應(yīng)用類加載器默認(rèn)的加載順序是:

(1).先從緩存中加載;
(2).如果沒(méi)有,則從JVM的Bootstrap類加載器加載;
(3).如果沒(méi)有,則從當(dāng)前類加載器加載(按照WEB-INF/classes、WEB-INF/lib的順序);
(4).如果沒(méi)有,則從父類加載器加載,由于父類加載器采用默認(rèn)的委派模式,所以加載順序是AppClassLoader、Common、Shared。

tomcat提供了delegate屬性用于控制是否啟用java委派模式,默認(rèn)false(不啟用),當(dāng)設(shè)置為true時(shí),tomcat將使用java的默認(rèn)委派模式,這時(shí)加載順序如下:

(1).先從緩存中加載;
(2).如果沒(méi)有,則從JVM的Bootstrap類加載器加載;
(3).如果沒(méi)有,則從父類加載器加載,加載順序是AppClassLoader、Common、Shared。
(4).如果沒(méi)有,則從當(dāng)前類加載器加載(按照WEB-INF/classes、WEB-INF/lib的順序);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,214評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,781評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,588評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,315評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,699評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,882評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,441評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,189評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,388評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,613評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,023評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,310評(píng)論 1 293
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,112評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,334評(píng)論 2 377

推薦閱讀更多精彩內(nèi)容