java ContextClassLoader (线程上下文类加载器)

一、基础知识点

知识点1: 每个ClassLoader都只能加载自己所绑定目录下的资源;

知识点2: 加载资源时的ClassLoader可以有多种选择:

1. 系统类加载器SystemClassLoader,可通过ClassLoader.getSystemClassLoader()获得;

2. 当前ClassLoader:加载了当前类的ClassLoader;

3. 线程上下文类加载器ContextClassLoader:Thread.currentThread().getContextClassLoader();

4. 自定义类加载器;

二、 ClassLoader和ContextClassLoader的区别

Bootstrap ClassLoader、ExtClassLoader、AppClassLoader,它们是真实存在的类,而且遵从”双亲委托“的机制。

而ContextClassLoader其实只是一个概念。

查看Thread.java源码可以发现

public class Thread implements Runnable {
/* The context ClassLoader for this thread */
   private ClassLoader contextClassLoader;
   public void setContextClassLoader(ClassLoader cl) {
       SecurityManager sm = System.getSecurityManager();
       if (sm != null) {
           sm.checkPermission(new RuntimePermission("setContextClassLoader"));
       contextClassLoader = cl;
   public ClassLoader getContextClassLoader() {
       if (contextClassLoader == null)
           return null;
       SecurityManager sm = System.getSecurityManager();
       if (sm != null) {
           ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                                  Reflection.getCallerClass());
       return contextClassLoader;
}

contextClassLoader只是一个成员变量,通过setContextClassLoader()方法设置,通过getContextClassLoader()设置。

二、线程上下文类加载器的产生原因

java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。

这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。 并且我们知道一个类由类加载器A加载,那么这个类依赖类也应该由相同的类加载器加载.那么问题来了, 引导类加载器 无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。

也就是说,双亲委派模型无法解决这个问题。

二、线程上下文类加载器的作用

ContextClassLoader的作用都是为了破坏Java类加载委托机制,使程序可以逆向使用类加载器。

比如:SPI的接口是Java核心库的一部分,是由 引导类加载器 (Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System类加载器)来加载的。

三、线程上下文类加载器的使用

线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。

类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。

如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,Thread默认集成父线程的 Context ClassLoader(注意,是父线程不是父类)。如果你整个应用中都没有对此作任何处理,那么 所有的线程都会以System ClassLoader作为线程的上下文类加载器。

在线程中运行的代码可以通过此类加载器来加载类和资源。

在实际使用时一般都用下面的经典结构: (获取-使用-还原)

ClassLoader targetClassLoader = null;// 外部参数
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
    Thread.currentThread().setContextClassLoader(targetClassLoader);
    // TODO
} catch (Exception e) {
    e.printStackTrace();
} finally {
    Thread.currentThread().setContextClassLoader(contextClassLoader);
  • 首先获取当前线程的线程上下文类加载器并保存到方法栈,然后将外部传递的类加载器设置为当前线程上下文类加载器
  • doSomething则可以利用新设置的类加载器做一些事情
  • 最后在设置当前线程上下文类加载器为老的类加载器

使用示例

public class ContextClassLoader {  
 @Test 
 public void testNewThreadContxtClassLoader(){  
        Thread t2 = new Thread();