##默認的三個類加載器 Java默認是有三個ClassLoader,按層次關系從上到下依次是: - Bootstrap ClassLoader - Ext ClassLoader - System ClassLoader Bootstrap ClassLoader是最頂層的ClassLoader,它比較特殊,是用C++編寫集成在JVM中的,是JVM啟動的時候用來加載一些核心類的,比如:`rt.jar`,`resources.jar`,`charsets.jar`,`jce.jar`等,可以運行下面代碼看都有哪些: ``` URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); } ``` 其余兩個ClassLoader都是繼承自`ClassLoader`這個類。Java的類加載采用了一種叫做“雙親委托”的方式(稍后解釋),所以除了`Bootstrap ClassLoader`其余的ClassLoader都有一個“父”類加載器, 不是通過繼承,而是一種包含的關系。 ``` //ClassLoader.java public abstract class ClassLoader { ... // The parent class loader for delegation private ClassLoader parent; ... ``` ##“雙親委托” 所謂“雙親委托”就是當加載一個類的時候會先委托給父類加載器去加載,當父類加載器無法加載的時候再嘗試自己去加載,所以整個類的加載是“自上而下”的,如果都沒有加載到則拋出`ClassNotFoundException`異常。 上面提到Bootstrap ClassLoader是最頂層的類加載器,實際上Ext ClassLoader和System ClassLoader就是一開始被它加載的。 Ext ClassLoader稱為擴展類加載器,負責加載Java的擴展類庫,默認加載JAVA_HOME/jre/lib/ext/目錄下的所有的jar(包括自己手動放進去的jar包)。 System ClassLoader叫做系統類加載器,負責加載應用程序classpath目錄下的所有jar和class文件,包括我們平時運行jar包指定cp參數下的jar包。 運行下面的代碼可以驗證上面內容: ``` ClassLoader loader = Debug.class.getClassLoader(); while(loader != null) { System.out.println(loader); loader = loader.getParent(); } System.out.println(loader); ``` ##“雙親委托”的作用 之所以采用“雙親委托”這種方式主要是為了安全性,避免用戶自己編寫的類動態替換Java的一些核心類,比如String,同時也避免了重復加載,因為JVM中區分不同類,不僅僅是根據類名,相同的class文件被不同的ClassLoader加載就是不同的兩個類,如果相互轉型的話會拋`java.lang.ClassCaseException`. ##自定義類加載器 除了上面說的三種默認的類加載器,用戶可以通過繼承`ClassLoader`類來創建自定義的類加載器,之所以需要自定義類加載器是因為有時候我們需要通過一些特殊的途徑創建類,比如網絡。 至於自定義類加載器是如何發揮作用的,`ClassLoader`類的loadClass方法已經把算法定義了: ``` protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } ``` >1. Invoke `findLoadedClass(String)` to check if the class has already been loaded. >2. Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead. >3. Invoke the `findClass(String)` method to find the class. 看上面的Javadoc可以知道,自定義的類加載器只要重載`findClass`就好了。 ##Context ClassLoader 首先Java中ClassLoader就上面提到的四種,`Bootstrap ClassLoader`,`Ext ClassLoader`,`System ClassLoader`以及用戶自定義的,所以`Context ClassLoader`並不是一種新的類加載器,肯定是這四種的一種。 首先關於類的加載補充一點就是如果類A是被一個加載器加載的,那么類A中引用的B也是由這個加載器加載的(如果B還沒有被加載的話),通常情況下就是類B必須在類A的classpath下。 但是考慮多線程環境下不同的對象可能是由不同的ClassLoader加載的,那么當一個由ClassLoaderC加載的對象A從一個線程被傳到另一個線程ThreadB中,而ThreadB是由ClassLoaderD加載的,這時候如果A想獲取除了自己的classpath以外的資源的話,它就可以通過`Thread.currentThread().getContextClassLoader()`來獲取線程上下文的ClassLoader了,一般就是ClassLoaderD了,可以通過`Thread.currentThread().setContextClassLoader(ClassLoader)`來顯示的設置。 ##為什么要有Context ClassLoader 之所以有Context ClassLoader是因為Java的這種“雙親委托”機制是有局限性的: - 舉網上的一個例子: > JNDI為例,JNDI的類是由bootstrap ClassLoader從rt.jar中間載入的,但是JNDI具體的核心驅動是由正式的實現提供的,並且通常會處於-cp參數之下(注:也就是默認的System ClassLoader管理),這就要求bootstartp ClassLoader去載入只有SystemClassLoader可見的類,正常的邏輯就沒辦法處理。怎么辦呢?parent可以通過獲得當前調用Thread的方法獲得調用線程的>Context ClassLoder 來載入類。 - 我上面提到的加載資源的例子。 `Contex ClassLoader`提供了一個突破這種機制的后門。 Context ClassLoader一般在一些框架代碼中用的比較多,平時寫代碼的時候用類的ClassLoader就可以了。 ##參考鏈接 [http://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader][1] [1]: http://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader