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

這是我們深入理解tomcat的第五篇文章,按照我們的思路,這次我們本應該區(qū)分析tomcat的連接器組件,但樓主思前想后,覺得連接器組件不能只是紙上談兵,需要深入源碼,但樓主本能的認為我們應該先分析tomcat的啟動過程,以能夠和我們上一篇文章深入理解 Tomcat(四)Tomcat 類加載器之為何違背雙親委派模型相銜接。因為啟動類加載器的核心代碼就在啟動過程中,所以,我決定先分析tomcat的啟動過程,結合源碼了解tomcat的類加載器如何實現(xiàn),以徹底了解tomcat的類加載器。

因為Tomcat 的啟動過程非常復雜,因此樓主將啟動過程拆開分析,不能像之前說的按照啟動過程分析了,否則,文章篇幅過長,條理也會變得不清晰,Tomcat的啟動過程包括了初始化容器的生命周期,還涉及到JMX的管理,還有我們現(xiàn)在分析的類加載器,因此,我們必須換個維度分析。

再一個,因為連接器和容器緊緊關聯(lián),連接器的作用就是分析http請求,所以,樓主覺得我們之前的計劃可能需要變更一下,我們將在分析完生命周期和類加載器之后將結合源碼分析連接器和容器,以了解tomcat的核心組件在接受HTTP請求后如何運行。

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

下面是這篇文章的目錄結構:

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

1. 啟動tomcat,進入main方法

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

可以看到樓主已經(jīng)寫了很多的注釋,因為樓主已經(jīng)debug過了.
我們看代碼,首先判斷”守護“對象是否為null,肯定為null了,然后進入if 塊, 創(chuàng)建一個默認構造器的Bootstrap對象,有一行注釋// Don't set daemon until init() has completed, 說不要在init方法完成之前設置daemon 變量,因為后面的很多步驟都依賴該變量,所以必須初始化結束后才能設置值,再繼續(xù)看,進入init方法:

2. 進入 init 方法

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

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

我們進入該方法看看

3. 進入 setCatalinaHome 方法

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

4. 進入 setCatalinaBase 方法

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


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

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

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

6. 進入 createClassLoader 方法

private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        // 從/org/apache/catalina/startup/catalina.properties 中獲取該 key 的配置
        // common.loader 對應的 Value=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
        String value = CatalinaProperties.getProperty(name + ".loader");
        // 如果不存在(默認存在), 返回 null
        if ((value == null) || (value.equals(""))){
            return parent;
        }
        // 使用環(huán)境變量對應的目錄替換字符串
        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的構造器構造類加載器.
        // 根據(jù)父類加載器是否為 null, URLClassLoader將啟用不同的構造器.
        // 總之, 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)所對應的value 不存在則返回父類加載器. 默認情況有.則繼續(xù)下面的邏輯.
  2. 處理從配置文件中獲取的字符串, 得到jar 包的位置.
  3. 使用ClassLoaderFactory.createClassLoader(repositories, parent)方法, 該方法使用一個繼承自java.net.URLClassLoader廢棄的StandardClassLoader類加載 jar 處理后得到的文件, 實際上調(diào)用的是URLClassLoader的構造方法, 下面代碼是 crateClassLoader 方法中最后的邏輯, array 中是 jar 包的地址,根據(jù)父類加載器是否為 null 調(diào)用StandardClassLoader不同的構造方法.
  4. 將ClassLoader 注冊到JMX服務中(這里涉及到生命周期的內(nèi)容,我們按下不表,后面再說)。

下面的是StandardClassLoader的構造方法,可以看到,實際上只是使用URLClassLoader 的構造方法:

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

7. 我們回到 initClassLoaders 方法中來

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

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


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

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

將 catalinaLoader 類加載器設置為當前線程上下文類加載器. 并設置線程安全類加載器.同時檢查是否安全, 如果不安全,則直接結束.

9. 設置完線程上下文類加載器之后做什么呢? 進入 securityClassLoad 方法


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

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

  • Tomcat核心class,即org.apache.catalina.core路徑下的class;
  • org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName;
  • Tomcat有關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. 進入 loadCorePackage 方法

我們以加載Tomcat核心class的loadCorePackage方法為例,我們看源碼實現(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, 意為"啟動對象實例", 然后使用反射調(diào)用該實例的setParentClassLoader 方法, 參數(shù)為 sharedLoader, 表示該實例的父類加載器為 sharedLoader.

最后, 設置 catalinaDaemon 為該實例。

12. 尋找 WebAppClassLoader, 進入 startInternal 方法

我們通過源碼知道了初始化了commonClassLoader, catalinaClassLoader, sharedLoader,但是,我們想起我們上一篇文章的圖,好像少了點什么?


WebAppClassLoader呢?

我們知道, WebAppClassLoaser 是各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見,那么他是如何初始化的呢?WebAppClassLoaser 的初始化時間和這3個類加載器初始化的時間不同,由于WebAppClassLoaser 和Context 緊緊關聯(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 方法用于子類去實現(xiàn), 我們看WebAppClassLoader的startInternal 方法核心實現(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. 進入 createClassLoader 方法

首先classLoader = createClassLoader();創(chuàng)建類加載器,并且設置其資源路徑為當前Webapp下某個context的類資源。最后我們看看createClassLoader的實現(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, 首先通過反射實例化classLoader?,F(xiàn)在我們知道了, WebappClassLoader 是在 StandardContext 初始化的時候實例化的,也證明了WebappClassLoader 和 Context 息息相關。

14. omcat 類加載結構體系創(chuàng)建完畢

至此,我們的Tomcat 類加載結構體系創(chuàng)建完畢。真TMD復雜啊?。?! 不過,請記住, 閱讀源碼是提高我們水平最快速的手段。源碼中大師們的設計模式和各種高級用法。會讓我們功力大增。繼續(xù)加油吧?。?!

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

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

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