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/
相关文章
相关标签/搜索