ClassLoader,Thread.currentThread().setContextClassLoader,tomcat的ClassLoader

實際上,在Java應用中全部程序都運行在線程裏,若是在程序中沒有手工設置過ClassLoader,對於通常的java類以下兩種方法得到的ClassLoader一般都是同一個 html

this.getClass.getClassLoader();  
Thread.currentThread().getContextClassLoader();  

方法一獲得的Classloader是靜態的,代表類的載入者是誰;java

方法二獲得的Classloader是動態的,誰執行(某個線程),就是那個執行者的Classloader。對於單例模式的類,靜態類等,載入一次後,這個實例會被不少程序(線程)調用,對於這些類,載入的Classloader和執行線程的Classloader一般都不一樣。c++

 

1、線程上下文類加載器

  線程上下文類加載器(context class loader)是從 JDK 1.2 開始引入的。類 java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用來獲取和設置線程的上下文類加載器。若是沒有經過 setContextClassLoader(ClassLoader cl)方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java 應用運行的初始線程的上下文類加載器是系統類加載器(appClassLoader)。在線程中運行的代碼能夠經過此類加載器來加載類和資源。web

  前面提到的類加載器的代理模式並不能解決 Java 應用開發中會遇到的類加載器的所有問題。Java 提供了不少服務提供者接口(Service Provider Interface,SPI),容許第三方爲這些接口提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫來提供,如 JAXP 的 SPI 接口定義包含在 javax.xml.parsers包中。這些 SPI 的實現代碼極可能是做爲 Java 應用所依賴的 jar 包被包含進來,能夠經過類路徑(CLASSPATH)來找到,如實現了 JAXP SPI 的 Apache Xerces所包含的 jar 包。SPI 接口中的代碼常常須要加載具體的實現類。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory類中的 newInstance()方法用來生成一個新的DocumentBuilderFactory的實例。這裏的實例的真正的類是繼承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的實現所提供的。如在 Apache Xerces 中,實現的類是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl而問題在於,SPI 的接口是 Java 核心庫的一部分,是由引導類加載器來加載的;SPI 實現的 Java 類通常是由系統類加載器來加載的。引導類加載器是沒法找到 SPI 的實現類的,由於它只加載 Java 的核心庫。它也不能代理給系統類加載器,由於它是系統類加載器的祖先類加載器。也就是說,類加載器的代理模式沒法解決這個問題apache

  線程上下文類加載器正好解決了這個問題。若是不作任何的設置,Java 應用的線程的上下文類加載器默認就是系統上下文類加載器。在 SPI 接口的代碼中使用線程上下文類加載器,就能夠成功的加載到 SPI 實現的類。線程上下文類加載器在不少 SPI 的實現中都會用到。bootstrap

JNDI,JDBC的訴求是:tomcat

  爲了能讓應用程序訪問到這些jar包中的實現類,即用appClassLoarder去加載這些實現類。能夠用getContextClassLoader取得當前線程的ClassLoader(即appClassLoarder),而後去加載這些實現類,就能讓應用訪問到安全

tomcat的訴求:app

  稍微跟上面有些不一樣,容器不但願它下面的webapps之間能互相訪問到,因此不能用appClassLoarder去加載。因此tomcat新建一個sharedClassLoader(它的parent是commonClassLoader,commonClassLoader的parent是appClassLoarder,默認狀況下,sharedClassLoader和commonClassLoader是同一個UrlClassLoader實例),這是catalina容器使用的ClassLoader。對於每一個webapp,爲其新建一個webappClassLoader,用於加載webapp下面的類,這樣webapp之間就不能相互訪問了。tomcat的ClassLoader不徹底遵循雙親委派,首先用webappClassLoader去加載某個類,若是找不到,再交給parent。而對於java核心庫,不在tomcat的ClassLoader的加載範圍。webapp

  看下tomcat的Bootstrap類的init方法:

public void init() throws Exception {

        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);//不知道這行設置了以後,對後面有什麼用???

        SecurityClassLoad.securityClassLoad(catalinaLoader);

        // Load our startup class and call its process() method/*反射實例化Catalina類的實例*/ Class<?> startupClass = catalinaLoader.loadClass ("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.newInstance();// Set the shared extensions class loader
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method = startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;

    }

 

  因爲Bootstrap類和catalina類被髮布在不一樣包裏面,Bootstrap對catalina實例的操做必須用反射完成。

  catalina類實例(即startupClass)由反射生成,它的ClassLoader是catalinaLoader。而後反射調用方法setParentClassLoader設置catalina類實例裏面的變量parentClassLoader爲sharedClassLoader,意思是做爲容器下webapp的webappClassLoader的parent,而不是設置catalina類的ClassLoader的parent是sharedClassLoader

  如今對tomcat的Bootstrap類的init方法裏面的Thread.currentThread().setContextClassLoader(catalinaLoader);這一行仍是很疑惑。由於,在類catalina裏面,能夠用getClass().getClassLoader()獲取catalinaClassLoader,不須要從Thread.currentThread().getContextClassLoader()方法得到。難道是爲了讓子線程的ClassLoader都是catalinaClassLoader,而不是appClassLoarder??

 

2、類加載器與 Web 容器

  對於運行在 Java EE™容器中的 Web 應用來講,類加載器的實現方式與通常的 Java 應用有所不一樣。不一樣的 Web 容器的實現方式也會有所不一樣。以 Apache Tomcat 來講,每一個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不一樣的是它是首先嚐試去加載某個類,若是找不到再代理給父類加載器。這與通常類加載器的順序是相反的。這是 Java Servlet 規範中的推薦作法,其目的是使得 Web 應用本身的類的優先級高於 Web 容器提供的類。這種代理模式的一個例外是:Java 核心庫的類是不在查找範圍以內的。這也是爲了保證 Java 核心庫的類型安全

  絕大多數狀況下,Web 應用的開發人員不須要考慮與類加載器相關的細節。下面給出幾條簡單的原則:

  • 每一個 Web 應用本身的 Java 類文件和使用的庫的 jar 包,分別放在 WEB-INF/classes和 WEB-INF/lib目錄下面。
  • 多個應用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由全部 Web 應用共享的目錄下面。
  • 當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確。

 

3、ContextClassLoader和其餘ClassLoader的關係 

  咱們能夠經過getContextClassLoader方法來得到此context classloader,就能夠用它來載入咱們所須要的Class。默認的是system classloader。

  bootstrap classloader  -------  對應jvm中某c++寫的dll類
  Extenson ClassLoader ---------對應內部類ExtClassLoader
  System ClassLoader  ---------對應內部類AppClassLoader
  Custom ClassLoader  ----------對應任何URLClassLoader的子類(你也能夠繼承SecureClassLoader或者更加nb一點 直接繼承ClassLoader,這樣的話你也是神通常的存在了 XD)

  以上四種classloder按照從上到下的順序,依次爲下一個的parent

  這個第一律念

  第二個概念是幾個有關的classloader的類

           抽象類 ClassLoader
                  |
            SecureClassLoader
                   |
            URLClassloader
             |           |                
 sun的ExtClassLoader   sun的AppClassLoader
  以上的類之間是繼承關係,與第一個概念說的parent是兩回事情,須要當心。

  第三個概念是Thread的ContextClassLoader
  其實從Context的名稱就能夠看出來,這只是一個用以存儲任何classloader引用的臨時存儲空間,與classloader的層次沒有任何關係。

 

4、Context ClassLoader詳解

  一般狀況下,類裝載器共有4種,即啓動類裝載器、EXT類裝載器、App類裝載器和自定義類裝載器。他們之間的階層狀況以下圖左面所示,他們都有着不一樣的載入規則,而且經過向上代理的方式來進行。而本文所提到的Context Class Loader並非一種新的裝載器類型,而是一種抽象的說法,它的具體表現形式爲:調用Thread.getCurrentThread().getContextClassLoader()所返回的那個ClassLoader。它和JVM缺省的類裝載器以及自定義類裝載之間是什麼關係呢?下面經過一個實驗來看一下。

 

 

 

3 實戰演練

(1)步驟一

  上圖進行了這樣一個實驗:首先一個名爲Class(1)的類中啓動MainThread(其實就是這個類裏面有main函數的意思啦),注意這個類的名字後面標出了其所在的路徑(即ClassPath),而後在裏面進行測試,發現目前它的裝載器和當前線程(MainThread)的ContextClassLoader都是AppClassLoader。而後Class(1)啓動了一個新線程Class(2)。這裏的Class(2)是一個Thread的子類,執行Class(2)代碼的線程我稱之爲Thread-0。

(2)步驟二

  上圖能夠看到Class(2)的裝載器和ContextClassLoader一樣都是AppClassLoader。隨後我在Class(2)中建立了一個新的URLCLassLoader,並用這個ClassLoader來載入另外一個和Class(1)不在同一個ClassPath下的類Class(3)。此時咱們就能夠看到變化:即載入Class(3)的裝載器是URLClassLoader,而ContextClassLoader還仍然是AppClassLoader。

(2)步驟三

  最後咱們在Class(3)中啓動了一個線程類Class(4),發現Class(4)也是由URLClassLoader載入的,而此時ContextClassLoader仍然是AppClassLoader。

    在整個過程當中,裝載類的ClassLoader發生了變化,因爲線程類Class(4)是由Class(3)啓動的,因此裝載它的類裝載器就變成了URLClassLoader。與此同時,全部線程的ContextClassLoader都繼承了生成該線程的ContextClassLoader--AppClassLoader。

 

  若是咱們在第二步的結尾執行了綠色框中的代碼:setContextClassLoader(),則結果就會變成下面這個樣子:

 

  咱們能夠清楚地看到,因爲Thread-0將其ContextClassLoader設置成了URLClassLoader,而Thread-1是在Thread-0裏面生成的,因此就繼承了其ContextClassLoader,變成了URLClassLoader。

 

3 後記

  這裏列出的試驗可能不見得全面,但相信足以說明問題,應該能夠說明ContextClassLoader與其它類裝載器的區別所在。但有可能ContextClassLoader還有其餘的不一樣之處,但願有這方面經驗的朋友一塊兒討論。

 

  Thread.currentThread().getContextClassLoader()的意義:

  父Classloader可使用當前線程Thread.currentthread().getContextLoader()中指定的classloader中加載的類。顛覆了父ClassLoader不能使用子Classloader或者是其它沒有直接父子關係的Classloader中加載的類這種狀況。這個就是Context Class Loader的意義。

 

5、Current ClassLoader

  當前類所屬的ClassLoader,在虛擬機中類之間引用,默認就是使用這個ClassLoader。另外,當你使用Class.forName(), Class.getResource()這幾個不帶ClassLoader參數的方法時,默認一樣使用當前類的ClassLoader。你能夠經過方法XX.class.GetClassLoader()獲取。

 

Reference

淺析Context Class Loader

ContextClassLoader淺析

https://www.ibm.com/developerworks/cn/java/j-lo-classloader/

http://blog.sina.com.cn/s/blog_605f5b4f01010i48.html

 http://my.oschina.net/u/571166/blog/212903

相關文章
相關標籤/搜索