深入理解 Tomcat(五)源碼剖析Tomcat 啟動過程----類加載過程

這是我們深入理解tomcat的第五篇文章玻蝌,按照我們的思路,這次我們本應(yīng)該區(qū)分析tomcat的連接器組件词疼,但樓主思前想后俯树,覺得連接器組件不能只是紙上談兵,需要深入源碼贰盗,但樓主本能的認(rèn)為我們應(yīng)該先分析tomcat的啟動過程许饿,以能夠和我們上一篇文章深入理解 Tomcat(四)Tomcat 類加載器之為何違背雙親委派模型相銜接。因?yàn)閱宇惣虞d器的核心代碼就在啟動過程中舵盈,所以陋率,我決定先分析tomcat的啟動過程,結(jié)合源碼了解tomcat的類加載器如何實(shí)現(xiàn)秽晚,以徹底了解tomcat的類加載器翘贮。

因?yàn)門omcat 的啟動過程非常復(fù)雜,因此樓主將啟動過程拆開分析爆惧,不能像之前說的按照啟動過程分析了狸页,否則,文章篇幅過長扯再,條理也會變得不清晰芍耘,Tomcat的啟動過程包括了初始化容器的生命周期,還涉及到JMX的管理熄阻,還有我們現(xiàn)在分析的類加載器斋竞,因此,我們必須換個維度分析秃殉。

再一個坝初,因?yàn)檫B接器和容器緊緊關(guān)聯(lián),連接器的作用就是分析http請求钾军,所以鳄袍,樓主覺得我們之前的計(jì)劃可能需要變更一下,我們將在分析完生命周期和類加載器之后將結(jié)合源碼分析連接器和容器吏恭,以了解tomcat的核心組件在接受HTTP請求后如何運(yùn)行拗小。

所以,今天樱哼,我們的任務(wù)就是debug tomcat 源碼哀九,分析tomcat啟動過程的每一步操作剿配。在看這篇文章之前希望同學(xué)們看看我們的第四篇分析tomcat的文章以了解tomcat的類加載器。

下面是這篇文章的目錄結(jié)構(gòu):

1. 啟動tomcat阅束,進(jìn)入main方法
2. 進(jìn)入 init 方法
3. 進(jìn)入 setCatalinaHome 方法
4. 進(jìn)入 setCatalinaBase 方法
5. 接下來則是類加載器大顯身手的時候了. 進(jìn)入 intiClassLoaders 方法
6. 進(jìn)入 createClassLoader 方法
7. 我們回到 initClassLoaders 方法中來
8. 再回到 init 方法中, 類加載器初始化結(jié)束, 接下來干嘛?
9. 設(shè)置完線程上下文類加載器之后做什么呢? 進(jìn)入 securityClassLoad 方法
10. 進(jìn)入 loadCorePackage 方法
11. 回到 init 方法
12. 尋找 WebAppClassLoader呼胚, 進(jìn)入 startInternal 方法
13. 進(jìn)入 createClassLoader 方法
14. tomcat 類加載結(jié)構(gòu)體系創(chuàng)建完畢

1. 啟動tomcat,進(jìn)入main方法

我們打開我們之前clone下的Tomcat-Source-Code 源碼息裸,找到Bootstrap 類蝇更,找到main方法,在451行打上斷點(diǎn)界牡,啟動main方法簿寂,開始我們的調(diào)試:

可以看到樓主已經(jīng)寫了很多的注釋漾抬,因?yàn)闃侵饕呀?jīng)debug過了.
我們看代碼宿亡,首先判斷”守護(hù)“對象是否為null,肯定為null了纳令,然后進(jìn)入if 塊挽荠, 創(chuàng)建一個默認(rèn)構(gòu)造器的Bootstrap對象,有一行注釋// Don't set daemon until init() has completed, 說不要在init方法完成之前設(shè)置daemon 變量平绩,因?yàn)楹竺娴暮芏嗖襟E都依賴該變量圈匆,所以必須初始化結(jié)束后才能設(shè)置值,再繼續(xù)看捏雌,進(jìn)入init方法:

2. 進(jìn)入 init 方法

該方法注釋:Initialize daemon.表明要初始化該守護(hù)程序跃赚,也就是這個變量Bootstrap:

我們看看該方法,首先setCatalinaHome()方法性湿,也就是我們啟動虛擬機(jī)的時候設(shè)置的 VM 參數(shù):

我們進(jìn)入該方法看看

3. 進(jìn)入 setCatalinaHome 方法

很明顯,我們設(shè)置過 Catalina.home , 所以獲取classpath下的catalina.home 的值不為null纬傲,所以直接return, 如果不為null肤频,則從項(xiàng)目根目錄下獲取boostrap的jar包叹括。如果存在,則設(shè)置上一級目錄為 catalina.home, 如果不存在,則設(shè)置項(xiàng)目根目錄為 catalina.home.這就是 setCatalinaHome 方法的邏輯.

4. 進(jìn)入 setCatalinaBase 方法

下一步是執(zhí)行 setCatalinaBase 方法, 也是一樣能獲取catalina.base, 直接 return,如果不存在, 則設(shè)置catalina.homecatalina.base, 如果catalina.home也是空,那么則設(shè)置項(xiàng)目根目錄為catatlina.base.


5. 接下來則是類加載器大顯身手的時候了. 進(jìn)入 intiClassLoaders 方法

intiClassLoaders(), 初始化多個類加載器. 我們進(jìn)入該方法查看具體邏輯:

首先,創(chuàng)建一個 common 類加載器, 父類加載器為 null, 注意: 如果是 java 推薦的類加載器機(jī)制, 父類加載器應(yīng)該是系統(tǒng)類加載器或者是擴(kuò)展類加載器, 所以這明顯違背了類加載器的雙親委派模型. 好, 我們繼續(xù)看 tomcat, 我們進(jìn)入 creatClassLoader 方法, 看看是如何實(shí)現(xiàn)的(該方法很長, 我們關(guān)注重要的邏輯):

6. 進(jìn)入 createClassLoader 方法

private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        // 從/org/apache/catalina/startup/catalina.properties 中獲取該 key 的配置
        // common.loader 對應(yīng)的 Value=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
        String value = CatalinaProperties.getProperty(name + ".loader");
        // 如果不存在(默認(rèn)存在), 返回 null
        if ((value == null) || (value.equals(""))){
            return parent;
        }
        // 使用環(huán)境變量對應(yīng)的目錄替換字符串
        value = replace(value);

        //Repository是ClassLoaderFactory  中的一個靜態(tài)內(nèi)部類, 有2個屬性, location, type, 表示某個位置的某種類型的文件
        List<Repository> repositories = new ArrayList<Repository>();
        /*
        省略部分代碼, 此部分代碼是處理value 變量并獲取文件位置和類型
        */  

        // 根據(jù)給定的路徑數(shù)組前去加載給定的 class 文件,
        // StandardClassLoader 繼承了 java.net.URLClassLoader ,使用URLClassLoader的構(gòu)造器構(gòu)造類加載器.
        // 根據(jù)父類加載器是否為 null, URLClassLoader將啟用不同的構(gòu)造器.
        // 總之, common 類加載器沒有指定父類加載器,違背雙親委派模型
        ClassLoader classLoader = ClassLoaderFactory.createClassLoader// 創(chuàng)建類加載器, 如果parent 是0,則
            (repositories, parent);

        // Retrieving MBean server // 注冊到 JMX 中管理 bean, 這個我們后面說
        MBeanServer mBeanServer = null;
        if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
            mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
        } else {
            mBeanServer = ManagementFactory.getPlatformMBeanServer();
        }

        // Register the server classloader
        ObjectName objectName =
            new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
        // 生命周期管理.
        mBeanServer.registerMBean(classLoader, objectName);

        return classLoader;

    }

雖然上面的代碼寫了注釋,但是我們還是梳理一下邏輯:

  1. /org/apache/catalina/startup/catalina.properties配置文件中獲取 lib 倉庫和 jar 包位置. 如果key (分別為common.loader、server.loader宵荒、shared.loader)所對應(yīng)的value 不存在則返回父類加載器. 默認(rèn)情況有.則繼續(xù)下面的邏輯.
  2. 處理從配置文件中獲取的字符串, 得到j(luò)ar 包的位置.
  3. 使用ClassLoaderFactory.createClassLoader(repositories, parent)方法, 該方法使用一個繼承自java.net.URLClassLoader廢棄的StandardClassLoader類加載 jar 處理后得到的文件, 實(shí)際上調(diào)用的是URLClassLoader的構(gòu)造方法, 下面代碼是 crateClassLoader 方法中最后的邏輯, array 中是 jar 包的地址,根據(jù)父類加載器是否為 null 調(diào)用StandardClassLoader不同的構(gòu)造方法.
  4. 將ClassLoader 注冊到JMX服務(wù)中(這里涉及到生命周期的內(nèi)容汁雷,我們按下不表,后面再說)报咳。

下面的是StandardClassLoader的構(gòu)造方法,可以看到,實(shí)際上只是使用URLClassLoader 的構(gòu)造方法:

而 URLClassLoader 將會調(diào)用父類 SecureClassLoader 的構(gòu)造方法, 而 SecureClassLoader 將會調(diào)用 ClassLoader 的構(gòu)造方法, 從而完成一個類加載器的初始化. 可謂不易.

7. 我們回到 initClassLoaders 方法中來

執(zhí)行完commonLoader = createClassLoader("common", null);, 接下里就會判斷返回的類加載器是否為 null, 什么時候會為 null 呢? 找不到配置文件中的 key 的時候或者 key 對應(yīng)的 value 為空的時候回返回 null, 如果返回 null, 那么就設(shè)置默認(rèn)的類加載器為 common 類加載器.

繼續(xù)初始化 catalinaloader 和 sharedLoader 兩個類加載器, 同樣調(diào)用 createClassLoader 方法, 不同的是, 他們的父類加載器不是 null, 而是上面的 common 類加載器. 我們看看他們進(jìn)入這個方法的時候回怎么樣?我們 debug 看一下:


可以看到兩個獲取到都是空, 所以他們直接返回父類加載器, 也就是說, 他們?nèi)齻€使用的是同一個類加載器.

8. 再回到 init 方法中, 類加載器初始化結(jié)束, 接下來干嘛?

將 catalinaLoader 類加載器設(shè)置為當(dāng)前線程上下文類加載器. 并設(shè)置線程安全類加載器.同時檢查是否安全, 如果不安全,則直接結(jié)束.

9. 設(shè)置完線程上下文類加載器之后做什么呢? 進(jìn)入 securityClassLoad 方法


可以看到厅篓,該類時加載Tomcat容器中類資源伙菊,傳遞的ClassLoader時catalinaLoader,也就是說,Tomcat容器的類資源都是catalinaLoader加載完成的叼耙。

securityClassLoad方法主要加載Tomcat容器所需的class,包括:

  • Tomcat核心class,即org.apache.catalina.core路徑下的class;
  • org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName柴底;
  • Tomcat有關(guān)session的class,即org.apache.catalina.session路徑下的class粱胜;
  • Tomcat工具類的class柄驻,即org.apache.catalina.util路徑下的class;
  • javax.servlet.http.Cookie焙压;
  • Tomcat處理請求的class鸿脓,即org.apache.catalina.connector路徑下的class;
  • Tomcat其它工具類的class涯曲,也是org.apache.catalina.util路徑下的class野哭;

10. 進(jìn)入 loadCorePackage 方法

我們以加載Tomcat核心class的loadCorePackage方法為例,我們看源碼實(shí)現(xiàn):

  private static final void loadCorePackage(ClassLoader loader)
        throws Exception {
        final String basePackage = "org.apache.catalina.core.";
        loader.loadClass
            (basePackage +
             "AccessLogAdapter");
        loader.loadClass
            (basePackage +
             "ApplicationContextFacade$1");
        loader.loadClass
            (basePackage +
             "ApplicationDispatcher$PrivilegedForward");
        loader.loadClass
            (basePackage +
             "ApplicationDispatcher$PrivilegedInclude");
        loader.loadClass
            (basePackage +
            "AsyncContextImpl");
        loader.loadClass
            (basePackage +
            "AsyncContextImpl$DebugException");
        loader.loadClass
            (basePackage +
            "AsyncContextImpl$1");
        loader.loadClass
            (basePackage +
            "AsyncContextImpl$PrivilegedGetTccl");
        loader.loadClass
            (basePackage +
            "AsyncContextImpl$PrivilegedSetTccl");
        loader.loadClass
            (basePackage +
            "AsyncListenerWrapper");
        loader.loadClass
            (basePackage +
             "ContainerBase$PrivilegedAddChild");
        loader.loadClass
            (basePackage +
             "DefaultInstanceManager$1");
        loader.loadClass
            (basePackage +
             "DefaultInstanceManager$2");
        loader.loadClass
            (basePackage +
             "DefaultInstanceManager$3");
        loader.loadClass
            (basePackage +
             "DefaultInstanceManager$AnnotationCacheEntry");
        loader.loadClass
            (basePackage +
             "DefaultInstanceManager$AnnotationCacheEntryType");
        loader.loadClass
            (basePackage +
             "ApplicationHttpRequest$AttributeNamesEnumerator");
    }

我們可以看到幻件,catalinaClassLoader 加載了該包下的類拨黔。根據(jù)我們之前的理解:catalinaClassLoader 加載的類是Tomcat容器私有的類加載器,加載路徑中的class對于Webapp不可見绰沥。

11. 回到 init 方法

首先打印一波日志, 然后使用類 catalinaLoader 類加載器加載 org.apache.catalina.startup.Catalina 類, 接著創(chuàng)建該類的一個對象, 名為 startupInstance, 意為"啟動對象實(shí)例", 然后使用反射調(diào)用該實(shí)例的setParentClassLoader 方法, 參數(shù)為 sharedLoader, 表示該實(shí)例的父類加載器為 sharedLoader.

最后, 設(shè)置 catalinaDaemon 為該實(shí)例篱蝇。

12. 尋找 WebAppClassLoader, 進(jìn)入 startInternal 方法

我們通過源碼知道了初始化了commonClassLoader, catalinaClassLoader, sharedLoader徽曲,但是零截,我們想起我們上一篇文章的圖,好像少了點(diǎn)什么秃臣?


WebAppClassLoader呢涧衙?

我們知道, WebAppClassLoaser 是各個Webapp私有的類加載器奥此,加載路徑中的class只對當(dāng)前Webapp可見弧哎,那么他是如何初始化的呢?WebAppClassLoaser 的初始化時間和這3個類加載器初始化的時間不同得院,由于WebAppClassLoaser 和Context 緊緊關(guān)聯(lián)傻铣,因此咋初始化
org.apache.catalina.core.StandardContext 會一起初始化 WebAppClassLoader, 該類中startInternal方法含有初始化類加載器的邏輯祥绞,核心源碼如下:

    @Override
    protected synchronized void startInternal() throws LifecycleException {
         if (getLoader() == null) {
              WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
              webappLoader.setDelegate(getDelegate());
              setLoader(webappLoader);
        }
        if ((loader != null) && (loader instanceof Lifecycle)) {
              ((Lifecycle) loader).start();
        }
    }

首先創(chuàng)建 WebAppClassLoader 非洲, 然后 setLoader(webappLoader),再調(diào)用start方法蜕径,該方法是個模板方法两踏,內(nèi)部有 startInternal 方法用于子類去實(shí)現(xiàn), 我們看WebAppClassLoader的startInternal 方法核心實(shí)現(xiàn):

    @Override
    protected void startInternal() throws LifecycleException {
            classLoader = createClassLoader();
            classLoader.setResources(container.getResources());
            classLoader.setDelegate(this.delegate);
            classLoader.setSearchExternalFirst(searchExternalFirst);
            if (container instanceof StandardContext) {
                classLoader.setAntiJARLocking(
                        ((StandardContext) container).getAntiJARLocking());
                classLoader.setClearReferencesStatic(
                        ((StandardContext) container).getClearReferencesStatic());
                classLoader.setClearReferencesStopThreads(
                        ((StandardContext) container).getClearReferencesStopThreads());
                classLoader.setClearReferencesStopTimerThreads(
                        ((StandardContext) container).getClearReferencesStopTimerThreads());
                classLoader.setClearReferencesHttpClientKeepAliveThread(
                        ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
            }

            for (int i = 0; i < repositories.length; i++) {
                classLoader.addRepository(repositories[i]);
            }

    }

13. 進(jìn)入 createClassLoader 方法

首先classLoader = createClassLoader();創(chuàng)建類加載器兜喻,并且設(shè)置其資源路徑為當(dāng)前Webapp下某個context的類資源梦染。最后我們看看createClassLoader的實(shí)現(xiàn):


    /**
     * Create associated classLoader.
     */
    private WebappClassLoader createClassLoader()
        throws Exception {

        Class<?> clazz = Class.forName(loaderClass);
        WebappClassLoader classLoader = null;

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

        return classLoader;

    }

這里的loaderClass 是 字符串 org.apache.catalina.loader.WebappClassLoader, 首先通過反射實(shí)例化classLoader。現(xiàn)在我們知道了, WebappClassLoader 是在 StandardContext 初始化的時候?qū)嵗呐潦叮沧C明了WebappClassLoader 和 Context 息息相關(guān)泛粹。

14. omcat 類加載結(jié)構(gòu)體系創(chuàng)建完畢

至此,我們的Tomcat 類加載結(jié)構(gòu)體系創(chuàng)建完畢肮疗。真TMD復(fù)雜熬фⅰ!N被酢们衙! 不過,請記住碱呼, 閱讀源碼是提高我們水平最快速的手段蒙挑。源碼中大師們的設(shè)計(jì)模式和各種高級用法。會讓我們功力大增愚臀。繼續(xù)加油吧R涫础!懊悯!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜓谋,一起剝皮案震驚了整個濱河市梦皮,隨后出現(xiàn)的幾起案子炭分,更是在濱河造成了極大的恐慌,老刑警劉巖剑肯,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捧毛,死亡現(xiàn)場離奇詭異,居然都是意外死亡让网,警方通過查閱死者的電腦和手機(jī)呀忧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溃睹,“玉大人而账,你說我怎么就攤上這事∫蚱” “怎么了泞辐?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長竞滓。 經(jīng)常有香客問我咐吼,道長,這世上最難降的妖魔是什么商佑? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任锯茄,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肌幽。我一直安慰自己晚碾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布喂急。 她就那樣靜靜地躺著迄薄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪煮岁。 梳的紋絲不亂的頭發(fā)上讥蔽,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機(jī)與錄音画机,去河邊找鬼冶伞。 笑死,一個胖子當(dāng)著我的面吹牛步氏,可吹牛的內(nèi)容都是我干的响禽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荚醒,長吁一口氣:“原來是場噩夢啊……” “哼芋类!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起界阁,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤侯繁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后泡躯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贮竟,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年较剃,在試婚紗的時候發(fā)現(xiàn)自己被綠了咕别。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡写穴,死狀恐怖惰拱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情啊送,我是刑警寧澤偿短,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站删掀,受9級特大地震影響翔冀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜披泪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一纤子、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦控硼、人聲如沸泽论。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翼悴。三九已至,卻和暖如春幔妨,著一層夾襖步出監(jiān)牢的瞬間鹦赎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工误堡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留古话,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓锁施,卻偏偏與公主長得像陪踩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悉抵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354

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