死磕tomcat源碼(二)之類加載體系

前言

Tomcat遵循J2EE規(guī)范粥血,實現(xiàn)了Web容器湿滓。很多有關web的書籍和文章都離不開對Tomcat的分析诗良,初學者可以從Tomcat的實現(xiàn)對J2EE有更深入的了解糠亩。此外虐骑,Tomcat還根據(jù)Java虛擬機規(guī)范實現(xiàn)了經典的雙親委派模式的類加載體系。本文基于Tomcat7.0的Java源碼赎线,對其類加載體系進行分析廷没。

概述

首先簡單介紹下Java虛擬機規(guī)范中提到的主要類加載器;

  • Bootstrap Loader:啟動類加載器主要加載的是JVM自身需要的類垂寥,這個類加載使用C++語言實現(xiàn)的颠黎,是虛擬機自身的一部分,它負責將 <JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數(shù)指定的路徑下的jar包加載到內存中滞项,注意必由于虛擬機是按照文件名識別加載jar包的狭归,如rt.jar,如果文件名不被虛擬機識別蓖扑,即使把jar包丟到lib目錄下也是沒有作用的(出于安全考慮唉铜,Bootstrap啟動類加載器只加載包名為java、javax律杠、sun等開頭的類)潭流。
  • Extended Loader:擴展類加載器是指Sun公司(已被Oracle收購)實現(xiàn)的sun.misc.Launcher$ExtClassLoader類,由Java語言實現(xiàn)的柜去,是Launcher的靜態(tài)內部類灰嫉,它負責加載<JAVA_HOME>/lib/ext目錄下或者由系統(tǒng)變量-Djava.ext.dir指定位路徑中的類庫,開發(fā)者可以直接使用標準擴展類加載器嗓奢。
  • AppClass Loader: 也稱應用程序加載器是指 Sun公司實現(xiàn)的sun.misc.Launcher$AppClassLoader讼撒。它負責加載系統(tǒng)類路徑java -classpath或-D java.class.path 指定路徑下的類庫,也就是我們經常用到的classpath路徑,開發(fā)者可以直接使用系統(tǒng)類加載器根盒,一般情況下該類加載是程序中默認的類加載器钳幅,通過ClassLoader#getSystemClassLoader()方法可以獲取到該類加載器。

根據(jù)java虛擬機的雙親委派模式的原則炎滞,類加載器在加載一個類時敢艰,首先交給父類加載器加載,層層往上直到Bootstrap Loader册赛。也就是一個類最先由Bootstrap Loader加載钠导,如果沒有加載到,則交給下一層的類加載器加載森瘪,如果沒有加載到牡属,則依次層層往下,直到最下層的類加載器扼睬。這也就是說逮栅,凡是能通過父一級類加載器加載到的類,對于子類也是可見的痰驱。因此可以利用雙親委派模式的特性证芭,使用類加載器對不同路徑下的jar包或者類進行環(huán)境隔離。

然后用一張圖片來展示Tomcat的類加載體系:


這里結合之前對雙親委派模式的類加載過程的描述担映,對上圖所示類加載體系進行介紹:
ClassLoader:Java提供的類加載器抽象類废士,用戶自定義的類加載器需要繼承實現(xiàn)
commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp訪問蝇完;
catalinaLoader:Tomcat容器私有的類加載器官硝,加載路徑中的class對于Webapp不可見;
sharedLoader:各個Webapp共享的類加載器短蜕,加載路徑中的class對于所有Webapp可見氢架,但是對于Tomcat容器不可見;

WebappClassLoader:各個Webapp私有的類加載器朋魔,加載路徑中的class只對當前Webapp可見岖研;

源碼分析

commonLoader、catalinaLoader和sharedLoader在Tomcat容器初始化的一開始警检,即調用Bootstrap的init方法時創(chuàng)建孙援。catalinaLoader會被設置為Tomcat主線程的線程上下文類加載器,并且使用catalinaLoader加載Tomcat容器自身容器下的class扇雕。Bootstrap的init方法的部分代碼見代碼清單1拓售。

代碼清單1 Bootstrap的init方法的部分實現(xiàn) :

    /**
     * Initialize daemon.
     */
    public void init()
        throws Exception
    {

        // Set Catalina path
        setCatalinaHome();
        setCatalinaBase();

        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);
        // 省略后邊的代碼

代碼清單1中,我們首先關注initClassLoaders方法的實現(xiàn)镶奉,見代碼清單2.initClassLoaders方法用來初始化commonLoader础淤、catalinaLoader崭放、sharedLoader。

代碼清單2 initClassLoaders方法的實現(xiàn):

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) {  
        log.error("Class loader creation threw exception", t);  
        System.exit(1);  
    }  
}  

從代碼清單2中看到創(chuàng)建類加載器是通過調用createClassLoader方法實現(xiàn)的鸽凶,createClassLoader的實現(xiàn)見代碼清單3.

private ClassLoader createClassLoader(String name, ClassLoader parent)  
    throws Exception {  
  
    String value = CatalinaProperties.getProperty(name + ".loader");  
    if ((value == null) || (value.equals("")))  
        return parent;  
  
    ArrayList<String> repositoryLocations = new ArrayList<String>();  
    ArrayList<Integer> repositoryTypes = new ArrayList<Integer>();  
    int i;  
  
    StringTokenizer tokenizer = new StringTokenizer(value, ",");  
    while (tokenizer.hasMoreElements()) {  
        String repository = tokenizer.nextToken();  
  
        // Local repository  
        boolean replace = false;  
        String before = repository;  
        while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {  
            replace=true;  
            if (i>0) {  
            repository = repository.substring(0,i) + getCatalinaHome()   
                + repository.substring(i+CATALINA_HOME_TOKEN.length());  
            } else {  
                repository = getCatalinaHome()   
                    + repository.substring(CATALINA_HOME_TOKEN.length());  
            }  
        }  
        while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {  
            replace=true;  
            if (i>0) {  
            repository = repository.substring(0,i) + getCatalinaBase()   
                + repository.substring(i+CATALINA_BASE_TOKEN.length());  
            } else {  
                repository = getCatalinaBase()   
                    + repository.substring(CATALINA_BASE_TOKEN.length());  
            }  
        }  
        if (replace && log.isDebugEnabled())  
            log.debug("Expanded " + before + " to " + repository);  
  
        // Check for a JAR URL repository  
        try {  
            new URL(repository);  
            repositoryLocations.add(repository);  
            repositoryTypes.add(ClassLoaderFactory.IS_URL);  
            continue;  
        } catch (MalformedURLException e) {  
            // Ignore  
        }  
  
        if (repository.endsWith("*.jar")) {  
            repository = repository.substring  
                (0, repository.length() - "*.jar".length());  
            repositoryLocations.add(repository);  
            repositoryTypes.add(ClassLoaderFactory.IS_GLOB);  
        } else if (repository.endsWith(".jar")) {  
            repositoryLocations.add(repository);  
            repositoryTypes.add(ClassLoaderFactory.IS_JAR);  
        } else {  
            repositoryLocations.add(repository);  
            repositoryTypes.add(ClassLoaderFactory.IS_DIR);  
        }  
    }  
  
    String[] locations = repositoryLocations.toArray(new String[0]);  
    Integer[] types = repositoryTypes.toArray(new Integer[0]);  
  
    ClassLoader classLoader = ClassLoaderFactory.createClassLoader  
        (locations, types, parent);  
  
    // Retrieving MBean server  
    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;  
  
}  

createClassLoader方法的執(zhí)行步驟如下:

  1. 獲取各個類加載器相應的資源配置文件(分別為common.loader币砂、server.loader、shared.loader)玻侥,從中獲取類資源路徑的配置信息道伟;
  2. 解析類資源路徑下的各個資源位置和類型,也包括對jar資源的檢查使碾;
  3. 調用ClassLoaderFactory.createClassLoader(locations, types, parent)方法創(chuàng)建ClassLoader;
  4. 將ClassLoader注冊到JMX服務中祝懂。

我們回頭看看代碼清單1中的SecurityClassLoad.securityClassLoad(catalinaLoader)的實現(xiàn)票摇,見代碼清單4.這說明加載Tomcat容器本身的類資源的確是使用catalinaLoader來完成的。

代碼清單4 securityClassLoad的實現(xiàn):

public static void securityClassLoad(ClassLoader loader)  
    throws Exception {  
  
    if( System.getSecurityManager() == null ){  
        return;  
    }  
      
    loadCorePackage(loader);  
    loadLoaderPackage(loader);  
    loadSessionPackage(loader);  
    loadUtilPackage(loader);  
    loadJavaxPackage(loader);  
    loadCoyotePackage(loader);          
    loadTomcatPackage(loader);  
}  

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笙纤;
我們以加載Tomcat核心class的loadCorePackage方法為例耗溜,其實現(xiàn)見代碼清單5所示。

代碼清單5 loadCorePackage的實現(xiàn):

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

至此省容,有關commonLoader抖拴、catalinaLoader和sharedLoader三個類加載器的初始化以及使用catalinaLoader加載Tomcat容器自身類資源的內容已經介紹完了,WebappClassLoader下次補上腥椒。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末阿宅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子寞酿,更是在濱河造成了極大的恐慌家夺,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伐弹,死亡現(xiàn)場離奇詭異拉馋,居然都是意外死亡榨为,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門煌茴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來随闺,“玉大人,你說我怎么就攤上這事蔓腐【乩郑” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵回论,是天一觀的道長散罕。 經常有香客問我,道長傀蓉,這世上最難降的妖魔是什么欧漱? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮葬燎,結果婚禮上误甚,老公的妹妹穿的比我還像新娘。我一直安慰自己谱净,他們只是感情好窑邦,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著壕探,像睡著了一般冈钦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上李请,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天派继,我揣著相機與錄音,去河邊找鬼捻艳。 笑死驾窟,一個胖子當著我的面吹牛,可吹牛的內容都是我干的认轨。 我是一名探鬼主播绅络,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嘁字!你這毒婦竟也來了恩急?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤纪蜒,失蹤者是張志新(化名)和其女友劉穎衷恭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纯续,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡随珠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年灭袁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窗看。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡茸歧,死狀恐怖,靈堂內的尸體忽然破棺而出显沈,到底是詐尸還是另有隱情软瞎,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布拉讯,位于F島的核電站涤浇,受9級特大地震影響,放射性物質發(fā)生泄漏魔慷。R本人自食惡果不足惜芙代,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盖彭。 院中可真熱鬧,春花似錦页滚、人聲如沸召边。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隧熙。三九已至,卻和暖如春幻林,著一層夾襖步出監(jiān)牢的瞬間贞盯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工沪饺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留躏敢,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓整葡,卻偏偏與公主長得像件余,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子遭居,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容