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

前言

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

概述

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

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

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

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


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

WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當(dāng)前Webapp可見;

源碼分析

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

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

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

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

        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);

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

代碼清單1中,我們首先關(guān)注initClassLoaders方法的實(shí)現(xiàn),見代碼清單2.initClassLoaders方法用來初始化commonLoader、catalinaLoader、sharedLoader。

代碼清單2 initClassLoaders方法的實(shí)現(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)建類加載器是通過調(diào)用createClassLoader方法實(shí)現(xiàn)的,createClassLoader的實(shí)現(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. 獲取各個類加載器相應(yīng)的資源配置文件(分別為common.loader、server.loader、shared.loader),從中獲取類資源路徑的配置信息;
  2. 解析類資源路徑下的各個資源位置和類型,也包括對jar資源的檢查;
  3. 調(diào)用ClassLoaderFactory.createClassLoader(locations, types, parent)方法創(chuàng)建ClassLoader;
  4. 將ClassLoader注冊到JMX服務(wù)中。

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

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

代碼清單5 loadCorePackage的實(shí)現(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");  
}  

至此,有關(guān)commonLoader、catalinaLoader和sharedLoader三個類加載器的初始化以及使用catalinaLoader加載Tomcat容器自身類資源的內(nèi)容已經(jīng)介紹完了,WebappClassLoader下次補(bǔ)上。

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

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

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