Tomcat7源碼分析(二)類加載體系

1、整體分析

    主流的Java Web服務器,如Tomcat、Jetty、WebLogic、WebSphere等都實現了本身定義的類加載器(通常都不止一個)。由於一個功能健全的Web服務器,須要解決以下的幾個問題:java

  1. 部署在同一個服務器上的兩個Web應用程序使用的Java 類庫能夠實現相互隔離,這是最基本的要求.兩個不一樣應用程序可能會依賴同一個第三方類庫的不一樣版本的,不能要求一個類庫在一個服務器中只有一份,服務器應當保證兩個應用程序的類庫能夠互相獨立使用
  2. 部署在同一個服務器上的兩個Web應用程序所使用的Java類庫能夠互相共享,這個需求也很常見,若是Java類庫不能共享使用,虛擬機的方法區很容易出現過分膨脹的風險
  3. 服務器須要儘量保證自身安全不受部署的Web應用程序影響.目前有許多主流的Java Web服務器都使用Java語言開發,所以服務器自己也有類庫依賴的問題,通常來講,基於安全的考慮,服務器所使用的類庫應該與應用程序使用的類庫互相獨立
  4. 支持JSP的服務器,大部分都須要支持HotSwap功能(熱交換功能)

    本文基於Tomcat7.0.69的Java源碼,對其類加載體系進行分析。web

    因爲上述的種種問題,在部署Web應用的時候若是隻使用一個單獨的ClassPath是沒法知足需求的,因此各類Web服務器都不約而同的提供了多個ClassPath路徑供用戶存在第三方類庫,這些路徑通常都以lib,classes命名,被放置到不一樣路徑的類庫,具有不一樣的訪問範圍和服務對象.tomcat服務器劃分用戶類庫結構和類加載描述以下,而後用一張圖片來展現Tomcat的類加載體系:各個類加載器之間不是繼承關係,而是一種委派關係。apache

這裏結合以前對雙親委派模式的類加載過程的描述,對上圖所示類加載體系進行介紹:
ClassLoader:Java提供的類加載器抽象類,用戶自定義的類加載器須要繼承實現
commonLoader:Tomcat最基本的類加載器,加載路徑中的class能夠被Tomcat容器自己以及各個Webapp訪問;
catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對於Webapp不可見;
sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對於全部Webapp可見,可是對於Tomcat容器不可見;緩存

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

經過下面類關係圖以及邏輯關係圖,同時對比上文內容梳理這些類加載器之間的關係。安全

一、類關係圖

classloader-3

從圖中看到了Common,Catalina,Shared類加載器是URLClassLoader類的一個實例,只是它們的類加載路徑不同,在tomcat/conf/catalina.properties配置文件中配置(common.loader,server.loader,shared.loader).WebAppClassLoader繼承自WebAppClassLoaderBase,基本全部邏輯都在WebAppClassLoaderBase爲中實現了,能夠看出tomcat的全部類加載器都是以URLClassLoader爲基礎進行擴展。服務器

二、邏輯關係圖

classloader-4

上面說到Common,Catalina,Shared類加載器是URLClassLoader類的一個實例,在默認的配置中,它們其實都是同一個對象,即commonLoader,結合初始化時的代碼(只保留關鍵代碼):session

private void initClassLoaders() {
        commonLoader = createClassLoader("common", null);  // commonLoader的加載路徑爲common.loader
        if( commonLoader == null ) {
            commonLoader=this.getClass().getClassLoader();
        }
        catalinaLoader = createClassLoader("server", commonLoader); // 加載路徑爲server.loader,默認爲空,父類加載器爲commonLoader
        sharedLoader = createClassLoader("shared", commonLoader); // 加載路徑爲shared.loader,默認爲空,父類加載器爲commonLoader
    }
 private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception {
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;      // catalinaLoader與sharedLoader的加載路徑均爲空,因此直接返回commonLoader對象,默認3者爲同一個對象
    }

 

    在上面的代碼初始化時很明確是指出了,catalina與shared類加載器的父類加載器爲common類加載器,而初始化commonClassLoader時父類加載器設置爲null,最終會調到createClassLoader靜態方法:數據結構

public static ClassLoader createClassLoader(List<Repository> repositories,
                                                final ClassLoader parent)
        throws Exception {
        .....
        return AccessController.doPrivileged(
                new PrivilegedAction<URLClassLoader>() {
                    @Override
                    public URLClassLoader run() {
                        if (parent == null)
                            return new URLClassLoader(array);  //該構造方法默認獲取系統類加載器爲父類加載器,即AppClassLoader
                        else
                            return new URLClassLoader(array, parent);
                    }
                });

    }

    在createClassLoader中指定參數parent==null時,最終會以系統類加載器(AppClassLoader)做爲父類加載器,這解釋了爲何commonClassLoader的父類加載器是AppClassLoader.app

一個web應用對應着一個StandardContext實例,每一個web應用都擁有獨立web應用類加載器(WebClassLoader),這個類加載器在StandardContext.startInternal()中被構造了出來:

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

    這裏getParentClassLoader()會獲取父容器StandarHost.parentClassLoader對象屬性,而這個對象屬性是在Catalina$SetParentClassLoaderRule.begin()初始化,初始化的值其實就是Catalina.parentClassLoader對象屬性,再來跟蹤一下Catalina.parentClassLoader,在Bootstrap.init()時經過反射調用了Catalina.setParentClassLoader(),將Bootstrap.sharedLoader屬性設置爲Catalina.parentClassLoader,因此WebClassLoader的父類加載器是Shared ClassLoader.

三、類加載邏輯

    Tomcat的類加載機制是違反了雙親委託原則的,對於一些未加載的非基礎類(Object,String等),各個web應用本身的類加載器(WebAppClassLoader)會優先加載,加載不到時再交給commonClassLoader走雙親委託。具體的加載邏輯位於WebAppClassLoaderBase.loadClass()方法中,代碼篇幅長,這裏以文字描述加載一個類過程:

  1. 先在本地緩存中查找是否已經加載過該類(對於一些已經加載了的類,會被緩存在resourceEntries這個數據結構中),若是已經加載即返回,不然 繼續下一步。
  2. 讓系統類加載器(AppClassLoader)嘗試加載該類,主要是爲了防止一些基礎類會被web中的類覆蓋,若是加載到即返回,返回繼續。
  3. 前兩步均沒加載到目標類,那麼web應用的類加載器將自行加載,若是加載到則返回,不然繼續下一步。
  4. 最後仍是加載不到的話,則委託父類加載器(Common ClassLoader)去加載。

第3第4兩個步驟的順序已經違反了雙親委託機制,除了tomcat以外,JDBC,JNDI,Thread.currentThread().setContextClassLoader();等不少地方都同樣是違反了雙親委託。

 

2、源碼分析

commonLoader、catalinaLoader和sharedLoader在Tomcat容器初始化的一開始,即調用Bootstrap的init方法時建立。catalinaLoader會被設置爲Tomcat主線程的線程上下文類加載器,而且使用catalinaLoader加載Tomcat容器自身容器下的class。Bootstrap的init方法的部分代碼見代碼清單1。

代碼清單1 Bootstrap的init方法的部分實現

 

[java] view plain copy

  1. /** 
  2.  * Initialize daemon. 
  3.  */  
  4. public void init()  
  5.     throws Exception  
  6. {  
  7.   
  8.     // Set Catalina path  
  9.     setCatalinaHome();  
  10.     setCatalinaBase();  
  11.   
  12.     initClassLoaders();  
  13.   
  14.     Thread.currentThread().setContextClassLoader(catalinaLoader);  
  15.   
  16.     SecurityClassLoad.securityClassLoad(catalinaLoader);  
  17.     // 省略後邊的代碼  

 

 代碼清單1中,咱們首先關注initClassLoaders方法的實現,見代碼清單2.initClassLoaders方法用來初始化commonLoader、catalinaLoader、sharedLoader。

代碼清單2 initClassLoaders方法的實現

 

[java] view plain copy

  1. private void initClassLoaders() {  
  2.     try {  
  3.         commonLoader = createClassLoader("common", null);  
  4.         if( commonLoader == null ) {  
  5.             // no config file, default to this loader - we might be in a 'single' env.  
  6.             commonLoader=this.getClass().getClassLoader();  
  7.         }  
  8.         catalinaLoader = createClassLoader("server", commonLoader);  
  9.         sharedLoader = createClassLoader("shared", commonLoader);  
  10.     } catch (Throwable t) {  
  11.         log.error("Class loader creation threw exception", t);  
  12.         System.exit(1);  
  13.     }  
  14. }  

 

 從代碼清單2中看到建立類加載器是經過調用createClassLoader方法實現的,createClassLoader的實現見代碼清單3.

代碼清單3 createClassLoader方法的實現

 

[java] view plain copy

  1. private ClassLoader createClassLoader(String name, ClassLoader parent)  
  2.     throws Exception {  
  3.   
  4.     String value = CatalinaProperties.getProperty(name + ".loader");  
  5.     if ((value == null) || (value.equals("")))  
  6.         return parent;  
  7.   
  8.     ArrayList<String> repositoryLocations = new ArrayList<String>();  
  9.     ArrayList<Integer> repositoryTypes = new ArrayList<Integer>();  
  10.     int i;  
  11.   
  12.     StringTokenizer tokenizer = new StringTokenizer(value, ",");  
  13.     while (tokenizer.hasMoreElements()) {  
  14.         String repository = tokenizer.nextToken();  
  15.   
  16.         // Local repository  
  17.         boolean replace = false;  
  18.         String before = repository;  
  19.         while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {  
  20.             replace=true;  
  21.             if (i>0) {  
  22.             repository = repository.substring(0,i) + getCatalinaHome()   
  23.                 + repository.substring(i+CATALINA_HOME_TOKEN.length());  
  24.             } else {  
  25.                 repository = getCatalinaHome()   
  26.                     + repository.substring(CATALINA_HOME_TOKEN.length());  
  27.             }  
  28.         }  
  29.         while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {  
  30.             replace=true;  
  31.             if (i>0) {  
  32.             repository = repository.substring(0,i) + getCatalinaBase()   
  33.                 + repository.substring(i+CATALINA_BASE_TOKEN.length());  
  34.             } else {  
  35.                 repository = getCatalinaBase()   
  36.                     + repository.substring(CATALINA_BASE_TOKEN.length());  
  37.             }  
  38.         }  
  39.         if (replace && log.isDebugEnabled())  
  40.             log.debug("Expanded " + before + " to " + repository);  
  41.   
  42.         // Check for a JAR URL repository  
  43.         try {  
  44.             new URL(repository);  
  45.             repositoryLocations.add(repository);  
  46.             repositoryTypes.add(ClassLoaderFactory.IS_URL);  
  47.             continue;  
  48.         } catch (MalformedURLException e) {  
  49.             // Ignore  
  50.         }  
  51.   
  52.         if (repository.endsWith("*.jar")) {  
  53.             repository = repository.substring  
  54.                 (0, repository.length() - "*.jar".length());  
  55.             repositoryLocations.add(repository);  
  56.             repositoryTypes.add(ClassLoaderFactory.IS_GLOB);  
  57.         } else if (repository.endsWith(".jar")) {  
  58.             repositoryLocations.add(repository);  
  59.             repositoryTypes.add(ClassLoaderFactory.IS_JAR);  
  60.         } else {  
  61.             repositoryLocations.add(repository);  
  62.             repositoryTypes.add(ClassLoaderFactory.IS_DIR);  
  63.         }  
  64.     }  
  65.   
  66.     String[] locations = repositoryLocations.toArray(new String[0]);  
  67.     Integer[] types = repositoryTypes.toArray(new Integer[0]);  
  68.   
  69.     ClassLoader classLoader = ClassLoaderFactory.createClassLoader  
  70.         (locations, types, parent);  
  71.   
  72.     // Retrieving MBean server  
  73.     MBeanServer mBeanServer = null;  
  74.     if (MBeanServerFactory.findMBeanServer(null).size() > 0) {  
  75.         mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);  
  76.     } else {  
  77.         mBeanServer = ManagementFactory.getPlatformMBeanServer();  
  78.     }  
  79.   
  80.     // Register the server classloader  
  81.     ObjectName objectName =  
  82.         new ObjectName("Catalina:type=ServerClassLoader,name=" + name);  
  83.     mBeanServer.registerMBean(classLoader, objectName);  
  84.   
  85.     return classLoader;  
  86.   
  87. }  

createClassLoader方法的執行步驟以下:

 

  1. 獲取各個類加載器相應的資源配置文件(分別爲common.loader、server.loader、shared.loader),從中獲取類資源路徑的配置信息;
  2. 解析類資源路徑下的各個資源位置和類型,也包括對jar資源的檢查;
  3. 調用ClassLoaderFactory.createClassLoader(locations, types, parent)方法建立ClassLoader;
  4. 將ClassLoader註冊到JMX服務中,有個JMX的內容能夠參照《Tomcat7.0源碼分析——生命週期管理 》一文中的相關介紹。

 

咱們回頭看看代碼清單1中的SecurityClassLoad.securityClassLoad(catalinaLoader)的實現,見代碼清單4.這說明加載Tomcat容器自己的類資源的確是使用catalinaLoader來完成的。

代碼清單4 securityClassLoad的實現

 

[java] view plain copy

  1. public static void securityClassLoad(ClassLoader loader)  
  2.     throws Exception {  
  3.   
  4.     if( System.getSecurityManager() == null ){  
  5.         return;  
  6.     }  
  7.       
  8.     loadCorePackage(loader);  
  9.     loadLoaderPackage(loader);  
  10.     loadSessionPackage(loader);  
  11.     loadUtilPackage(loader);  
  12.     loadJavaxPackage(loader);  
  13.     loadCoyotePackage(loader);          
  14.     loadTomcatPackage(loader);  
  15. }  

 

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方法爲例,其實現見代碼清單5所示。

代碼清單5 loadCorePackage的實現

 

[java] view plain copy

  1. private final static void loadCorePackage(ClassLoader loader)  
  2.     throws Exception {  
  3.     String basePackage = "org.apache.catalina.";  
  4.     loader.loadClass  
  5.         (basePackage +  
  6.          "core.ApplicationContextFacade$1");  
  7.     loader.loadClass  
  8.         (basePackage +  
  9.          "core.ApplicationDispatcher$PrivilegedForward");  
  10.     loader.loadClass  
  11.         (basePackage +  
  12.          "core.ApplicationDispatcher$PrivilegedInclude");  
  13.     loader.loadClass  
  14.         (basePackage +  
  15.         "core.AsyncContextImpl");  
  16.     loader.loadClass  
  17.         (basePackage +  
  18.         "core.AsyncContextImpl$AsyncState");  
  19.     loader.loadClass  
  20.         (basePackage +  
  21.         "core.AsyncContextImpl$DebugException");  
  22.     loader.loadClass  
  23.         (basePackage +  
  24.         "core.AsyncContextImpl$1");  
  25.     loader.loadClass  
  26.         (basePackage +  
  27.         "core.AsyncContextImpl$2");  
  28.     loader.loadClass  
  29.         (basePackage +  
  30.         "core.AsyncListenerWrapper");  
  31.     loader.loadClass  
  32.         (basePackage +  
  33.          "core.ContainerBase$PrivilegedAddChild");  
  34.     loader.loadClass  
  35.         (basePackage +  
  36.          "core.DefaultInstanceManager$1");  
  37.     loader.loadClass  
  38.         (basePackage +  
  39.          "core.DefaultInstanceManager$2");  
  40.     loader.loadClass  
  41.         (basePackage +  
  42.          "core.DefaultInstanceManager$3");  
  43.     loader.loadClass  
  44.         (basePackage +  
  45.          "core.DefaultInstanceManager$4");  
  46.     loader.loadClass  
  47.         (basePackage +  
  48.          "core.DefaultInstanceManager$5");  
  49.     loader.loadClass  
  50.         (basePackage +  
  51.          "core.ApplicationHttpRequest$AttributeNamesEnumerator");  
  52. }  

 

 至此,有關commonLoader、catalinaLoader和sharedLoader三個類加載器的初始化以及使用catalinaLoader加載Tomcat容器自身類資源的內容已經介紹完了,可是咱們尚未看到WebappClassLoader。啓動StandardContext的時候會建立WebappLoader,根據《Tomcat7.0源碼分析——生命週期管理 》一文的內容,咱們知道啓動StandardContext時會最終調用其startInternal方法,其實現見代碼清單6.

代碼清單6 StandardContext的startInternal方法

 

[java] view plain copy

  1. /** 
  2.  * Start this component and implement the requirements 
  3.  * of {@link LifecycleBase#startInternal()}. 
  4.  * 
  5.  * @exception LifecycleException if this component detects a fatal error 
  6.  *  that prevents this component from being used 
  7.  */  
  8. @Override  
  9. protected synchronized void startInternal() throws LifecycleException {  
  10.   
  11.     // 省略前邊的代碼   
  12.   
  13.     if (getLoader() == null) {  
  14.         WebappLoader webappLoader = new WebappLoader(getParentClassLoader());  
  15.         webappLoader.setDelegate(getDelegate());  
  16.         setLoader(webappLoader);  
  17.     }  
  18.    // 省略中間的代碼   
  19.    // Start our subordinate components, if any  
  20.    if ((loader != null) && (loader instanceof Lifecycle))  
  21.         ((Lifecycle) loader).start();   
  22.    // 省略後邊的代碼   
  23. }  

 

 從代碼清單6看到首先建立WebappLoader實例,而後調用WebappLoader的start方法,start又調用了startInternal方法,WebappLoader的startInternal的實現見代碼清單7.

代碼清單7 WebappLoader的startInternal實現

 

[java] view plain copy

  1. /** 
  2.  * Start associated {@link ClassLoader} and implement the requirements 
  3.  * of {@link LifecycleBase#startInternal()}. 
  4.  * 
  5.  * @exception LifecycleException if this component detects a fatal error 
  6.  *  that prevents this component from being used 
  7.  */  
  8. @Override  
  9. protected void startInternal() throws LifecycleException {  
  10.       
  11.     // Register a stream handler factory for the JNDI protocol  
  12.     URLStreamHandlerFactory streamHandlerFactory =  
  13.         new DirContextURLStreamHandlerFactory();  
  14.     if (first) {  
  15.         first = false;  
  16.         try {  
  17.             URL.setURLStreamHandlerFactory(streamHandlerFactory);  
  18.         } catch (Exception e) {  
  19.             // Log and continue anyway, this is not critical  
  20.             log.error("Error registering jndi stream handler", e);  
  21.         } catch (Throwable t) {  
  22.             // This is likely a dual registration  
  23.             log.info("Dual registration of jndi stream handler: "   
  24.                      + t.getMessage());  
  25.         }  
  26.     }  
  27.   
  28.     // Construct a class loader based on our current repositories list  
  29.     try {  
  30.   
  31.         classLoader = createClassLoader();  
  32.         classLoader.setResources(container.getResources());  
  33.         classLoader.setDelegate(this.delegate);  
  34.         classLoader.setSearchExternalFirst(searchExternalFirst);  
  35.         if (container instanceof StandardContext) {  
  36.             classLoader.setAntiJARLocking(  
  37.                     ((StandardContext) container).getAntiJARLocking());  
  38.             classLoader.setClearReferencesStatic(  
  39.                     ((StandardContext) container).getClearReferencesStatic());  
  40.             classLoader.setClearReferencesStopThreads(  
  41.                     ((StandardContext) container).getClearReferencesStopThreads());  
  42.             classLoader.setClearReferencesStopTimerThreads(  
  43.                     ((StandardContext) container).getClearReferencesStopTimerThreads());  
  44.             classLoader.setClearReferencesThreadLocals(  
  45.                     ((StandardContext) container).getClearReferencesThreadLocals());  
  46.         }  
  47.   
  48.         for (int i = 0; i < repositories.length; i++) {  
  49.             classLoader.addRepository(repositories[i]);  
  50.         }  

 

 咱們看到代碼清單7中經過調用createClassLoader來建立類加載器,而且設置其資源路徑爲當前Webapp下某個context的類資源。最後咱們看看createClassLoader的實現,見代碼清單8.

代碼清單8 createClassLoader的實現

 

[java] view plain copy

  1. /** 
  2.  * Create associated classLoader. 
  3.  */  
  4. private WebappClassLoader createClassLoader()  
  5.     throws Exception {  
  6.   
  7.     //loaderClass即字符串org.apache.catalina.loader.WebappClassLoader  
  8.     Class<?> clazz = Class.forName(loaderClass);  
  9.     WebappClassLoader classLoader = null;  
  10.   
  11.     if (parentClassLoader == null) {  
  12.         parentClassLoader = container.getParentClassLoader();  
  13.     }  
  14.     Class<?>[] argTypes = { ClassLoader.class };  
  15.     Object[] args = { parentClassLoader };  
  16.     Constructor<?> constr = clazz.getConstructor(argTypes);  
  17.     classLoader = (WebappClassLoader) constr.newInstance(args);  
  18.   
  19.     return classLoader;  
  20.   
  21. }  

 

 這裏loaderClass的值是字符串org.apache.catalina.loader.WebappClassLoader,經過反射來實例化WebappClassLoader。因爲每一個Webapp下的類資源由不一樣的WebappClassLoader負責加載,所以Webapp下各個Context的類資源是獨立的。至此,整個Tomcat的類加載體系構建完畢。

此外每一個jsp爲了實現熱替換,會有專門的類加載器負責加載。

參考文檔:http://blog.csdn.net/beliefer/article/details/50995516

《深刻理解Java虛擬機》

http://blog.csdn.net/czmacd/article/details/54017027

相關文章
相關標籤/搜索