Java類加載器之線程上下文類加載器(ContextClassLoader)

Thread.setContextClassLoader(ClassLoader cl)

在Java中提供了對於線程設置ContextClassLoader的方法,關於上下文類加載器,下面摘抄的內容將的比較明白:html

線程上下文類加載器(context class loader)是從 JDK 1.2 開始引入的。類 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用來獲取和設置線程的上下文類加載器。若是沒有經過 setContextClassLoader(ClassLoader cl)方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼能夠經過此類加載器來加載類和資源。 前面提到的類加載器的代理模式並不能解決 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 的核心庫。它也不能代理給系統類加載器,由於它是系統類加載器的祖先類加載器。也就是說,類加載器的代理模式沒法解決這個問題。 線程上下文類加載器正好解決了這個問題。若是不作任何的設置,Java 應用的線程的上下文類加載器默認就是系統上下文類加載器。在 SPI 接口的代碼中使用線程上下文類加載器,就能夠成功的加載到 SPI 實現的類。線程上下文類加載器在不少 SPI 的實現中都會用到。java

簡單來講,Java上下文類加載器的做用就是爲了SPI機制才存在的,在Java的類加載機制中,默認都是雙親委派機制,即一個類加載器在加載類的時候,會首先委派其父加載器去加載,若是父加載器加載不到,纔會本身加載。即: ClassLoader A -> System class loader -> Extension class loader -> Bootstrap class loader 在加載類的時候,是從左到右委派的,可是對於SPI來講,有些接口是java核心類庫提供的,而java核心類庫的java類是由引導類加載器負責加載的,而這些接口的實現類卻來自不一樣的jar包,java的類引導加載器是不會加載其餘來源的jar包的,這樣傳統的雙親委派機制就不能知足SPI的需求。而經過給當前線程設置上下文類加載器,就能夠由設置的上下文類加載器來實現對於接口實現類的加載。 java提供是jdbc Driver就是基於SPI的,咱們能夠用它作例子。mysql

jdbc Driver加載

咱們能夠經過java提供的ServiceLoader來實現一下,從而更好的理解java上下文類加載器的做用。 首先咱們在項目中加入Mysql connector jar包:sql

<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.18</version>
	</dependency>

而後咱們通道ServiceLoader加載Driver類的實現類。apache

經過默認的系統類加載器加載

@Test
	public void testClassLoader() {
		ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
		Iterator<Driver> iterator = loader.iterator();
		while (iterator.hasNext()) {
			Driver driver = (Driver) iterator.next();
			System.out.println("driver:" + driver.getClass() + ",loader:" + driver.getClass().getClassLoader());
		}
	}

爲何說是用系統默認的類加載器?咱們看一下ServiceLoader.load方法:oracle

public static <S> ServiceLoader<S> load(Class<S> service) {
	ClassLoader cl = Thread.currentThread().getContextClassLoader();
	return ServiceLoader.load(service, cl);
    }

能夠看到,它的實現就是首先獲取了當前線程的上下文類加載器,而上面的代碼,當前線程的上下文類加載器就是ClassLoader.getSystemClassLoader()。 運行結果:eclipse

driver:class sun.jdbc.odbc.JdbcOdbcDriver,loader:null
driver:class com.mysql.jdbc.Driver,loader:sun.misc.Launcher$AppClassLoader@53004901

能夠看到,有兩個driver的實現,一個是jdk自帶的,它是由引導類加載器加載的(null就表示引導類加載器),然後面的mysql實現的Driver的加載器就是系統類加載器。ide

如今咱們若是人工干預一下,不使用當前線程的上下文類加載器,咱們看看可否加載到mysql提供的實現類:測試

@Test
	public void testClassLoader() {
		ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class, ClassLoader.getSystemClassLoader().getParent());
		Iterator<Driver> iterator = loader.iterator();
		while (iterator.hasNext()) {
			Driver driver = (Driver) iterator.next();
			System.out.println("driver:" + driver.getClass() + ",loader:" + driver.getClass().getClassLoader());
		}
		System.out.println("system loader:" + ClassLoader.getSystemClassLoader());
		System.out.println("system loader's parent:" + ClassLoader.getSystemClassLoader().getParent());
	}

此次咱們將ServiceLoader使用的加載器人工設置成當前系統加載器的父加載器,而後再看下輸出結果:ui

driver:class sun.jdbc.odbc.JdbcOdbcDriver,loader:null
system loader:sun.misc.Launcher$AppClassLoader@53004901
system loader's parent:sun.misc.Launcher$ExtClassLoader@37b90b39

能夠看到,這裏java自帶的JdbcOdbcDriver仍是能夠加載的,可是mysql的卻沒有加載到,由於咱們使用的是ExtClassLoader,而該ClassLoader只會加載java本身擴展庫中的類,而不會加載mysql實現的類。

ClassLoader.getSystemClassLoader()

該方法會返回當前系統默認的類加載器。

public class Main {
	public static void main(String[] args) {
		System.out.println(ClassLoader.getSystemClassLoader());
	}
}

結果:

sun.misc.Launcher$AppClassLoader@53004901

當時該方法並非在全部狀況下都返回APPClassLoader,若是用戶自定義一個ClassLoader,則能夠經過設置JVM參數java.system.class.loader替換當前默認的ClassLoader。

自定義ClassLoader實現

首先咱們自定義一個ClassLoader,而後代碼運行的時候設置JVM參數java.system.class.loader爲咱們自定義的ClassLoader 咱們本身定義的ClassLoader

package loader;

public class MyClassLoader extends ClassLoader {
    //繼承了ClassLoader,可是沒有任何實現
}

測試類:

package loader;

public class Main {
	public static void main(String[] args) {
		System.out.println(ClassLoader.getSystemClassLoader());
	}
}

在運行以前,修改系統參數java.system.class.loader的值爲loader.MyClassLoader,eclipse中能夠經過以下方式設置: 設置JVM參數

設置完成以後咱們點擊運行,輸出下面的結果:

Error occurred during initialization of VM
java.lang.Error: java.lang.NoSuchMethodException: loader.MyClassLoader.<init>(java.lang.ClassLoader)
	at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
	at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)
Caused by: java.lang.NoSuchMethodException: loader.MyClassLoader.<init>(java.lang.ClassLoader)
	at java.lang.Class.getConstructor0(Unknown Source)
	at java.lang.Class.getDeclaredConstructor(Unknown Source)
	at java.lang.SystemClassLoaderAction.run(Unknown Source)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
	at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)

居然出錯了,意思是沒有init方法,咱們查看ClassLoader.getSystemClassLoader()方法的文檔,有下面一段:

If the system property "java.system.class.loader" is defined when this method is first invoked then the value of that property is taken to be the name of a class that will be returned as the system class loader. The class is loaded using the default system class loader and must define a public constructor that takes a single parameter of type ClassLoader which is used as the delegation parent. An instance is then created using this constructor with the default system class loader as the parameter. The resulting class loader is defined to be the system class loader.

意思就是,若是在ClassLoader.getSystemClassLoader()方法第一次執行的時候,指定了系統變量java.system.class.loader的值(就是說用戶本身設置了SystemClassLoader,那麼該方法將會返回用戶定義的ClassLoader,可是用戶自定義的ClassLoader必須定義一個公有的構造方法,該方法須要接受一個ClassLoader類型的參數,該參數將做爲用戶自定義ClassLoader的父ClassLoader。

很明顯,咱們上面本身實現的MyClassLoader並無任何實現,因此也就違背了這個規則,因此就會報錯。咱們將代碼修改一下:

package loader;

public class MyClassLoader extends ClassLoader {

	public MyClassLoader() {
		super();
	}

	public MyClassLoader(ClassLoader parent) {
		super(parent);
	}

}

其餘地方不變,而後再次執行,結果以下:

loader.MyClassLoader@2e6e1408

能夠看到,經過設置系統變量java.system.class.loader爲咱們自定義的ClassLoader,ClassLoader.getSystemClassLoader()返回的就是用戶本身定義的ClassLoader。

可是:用戶本身定義的ClassLoader是由哪一個ClassLoader加載的呢? 咱們能夠經過下面的代碼輸出來看一下:

package loader;

public class Main {
	public static void main(String[] args) {
		System.out.println("system loader:" + ClassLoader.getSystemClassLoader());
		System.out.println("system loader's parent:" + ClassLoader.getSystemClassLoader().getParent());
		System.out.println("system loader's loader:" + ClassLoader.getSystemClassLoader().getClass().getClassLoader());
	}
}

結果以下:

system loader:loader.MyClassLoader@5d888759
system loader's parent:sun.misc.Launcher$AppClassLoader@425224ee
system loader's loader:sun.misc.Launcher$AppClassLoader@425224ee

因此說,實際上用戶本身定義的ClassLoader是由原來的SystemClassLoader來加載的。

須要注意的是,上面設置系統變量java.system.class.loader的時候是經過上圖中的JVM參數指定的,若是經過program arguments則不會生效,一樣道理,下面的設置方式也是不生效的:

public class Main {
	public static void main(String[] args) {
		System.setProperty("java.system.class.loader", "loader.MyClassLoader");
		System.out.println(ClassLoader.getSystemClassLoader());
	}
}

參考資料

  1. http://7xo4v8.com1.z0.glb.clouddn.com/%40%2Fdocs%2FDynamicClassLoadingInTheJavaVirtualMachine.pdf
  2. http://docs.oracle.com/javase/7/docs/technotes/tools/findingclasses.html#srcfiles
  3. http://stackoverflow.com/questions/11395074/what-loads-the-java-system-classloader
  4. https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
  5. http://www.javaworld.com/article/2077344/core-java/find-a-way-out-of-the-classloader-maze.html
  6. http://stackoverflow.com/questions/7039467/java-serviceloader-with-multiple-classloaders
  7. http://www.cnblogs.com/549294286/p/3714692.html
  8. http://www.ibm.com/developerworks/cn/java/j-dyn0429/
  9. http://renchx.com/java-spi-serviceloader/
相關文章
相關標籤/搜索