ContextClassLoader是一种与线程相关的类加载器,类似ThreadLocal,每个线程对应一个上下文类加载器.在实际使用时一般都用下面的经典结构:

ClassLoader targetClassLoader = null;// 外部参数ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {Thread.currentThread().setContextClassLoader(targetClassLoader);// TODO
} catch (Exception e) {e.printStackTrace();
} finally {Thread.currentThread().setContextClassLoader(contextClassLoader);
}
  1. 首先获取当前线程的线程上下文类加载器并保存到方法栈,然后将外部传递的类加载器设置为当前线程上下文类加载器
  2. doSomething则可以利用新设置的类加载器做一些事情
  3. 最后在设置当前线程上下文类加载器为老的类加载器
上面的使用场景是什么?Java默认的类加载机制是委托机制,但是有些时候需要破坏这种固定的机制

具体来说,比如Java中的SPI(Service Provider Interface)是面向接口编程的,服务规则提供者会在JRE的核心API里面提供服务访问接口,而具体的实现则由其他开发商提供.我们知道Java核心API,比如rt.jar包,是使用Bootstrap ClassLoader加载的,而用户提供的jar包再由AppClassLoader加载.并且我们知道一个类由类加载器A加载,那么这个类依赖类也应该由相同的类加载器加载.那么Bootstrap ClassLoader加载了服务提供者在rt.jar里面提供的搜索开发上提供的实现类的API类(ServiceLoader),那么这些API类里面依赖的类应该也是有Bootstrap ClassLoader来加载.而上面说了用户提供的Jar包有AppClassLoader加载,所以需要一种违反双亲委派模型的方法,线程上下文类加载器ContextClassLoader就是为了解决这个问题。

下面使用JDBC来具体说明,JDBC是基于SPI机制来发现驱动提供商提供的实现类,提供者只需在JDBC实现的jar的META-INF/services/java.sql.Driver文件里指定实现类的方式暴露驱动提供者.例如:MYSQL实现的jar如下:

其中MYSQL的驱动如下实现了java.sql.Driver

public class Driver extends NonRegisteringDriver implements java.sql.Driver

引入MySQL驱动的jar包,测试类如下:

import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;public class MySQLClassLoader {public static void main(String[] args) {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());}System.out.println("current thread contxtloader:" + Thread.currentThread().getContextClassLoader());System.out.println("ServiceLoader loader:" + ServiceLoader.class.getClassLoader());}}

执行结果如下:

driver:class com.mysql.jdbc.Driver,loader:sun.misc.Launcher$AppClassLoader@2a139a55
driver:class com.mysql.fabric.jdbc.FabricMySQLDriver,loader:sun.misc.Launcher$AppClassLoader@2a139a55
driver:class com.alibaba.druid.proxy.DruidDriver,loader:sun.misc.Launcher$AppClassLoader@2a139a55
driver:class com.alibaba.druid.mock.MockDriver,loader:sun.misc.Launcher$AppClassLoader@2a139a55
current thread contxtloader:sun.misc.Launcher$AppClassLoader@2a139a55
ServiceLoader loader:null

从执行结果中可以知道ServiceLoader的加载器为Bootstrap,因为这里输出了null,并且从该类在rt.jar里面,也可以证明.

当前线程上下文类加载器为AppClassLoader.而com.mysql.jdbc.Driver则使用AppClassLoader加载.我们知道如果一个类中引用了另外一个类,那么被引用的类也应该由引用方类加载器来加载,而现在则是引用方ServiceLoader使用BootStartClassLoader加载,被引用方则使用子加载器APPClassLoader来加载了.是不是很诡异.

下面来看一下ServiceLoader的load方法源码:

public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
{return new ServiceLoader<>(service, loader);
}public static <S> ServiceLoader<S> load(Class<S> service) {// 获取当前线程上下文加载器,这里是APPClassLoaderClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);
}

上述代码获得了线程上下文加载器(其实就是AppClassLoader),并将该类加载器传递到下面的ServiceLoader类的构造方法loader成员变量中:

private ServiceLoader(Class<S> svc, ClassLoader cl) {service = Objects.requireNonNull(svc, "Service interface cannot be null");loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload();
}

上述loader变量什么时候使用的?看一下下面的代码:

 public S next() {if (acc == null) {return nextService();} else {PrivilegedAction<S> action = new PrivilegedAction<S>() {public S run() { return nextService(); }};return AccessController.doPrivileged(action, acc);}
}
private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?> c = null;try {// 使用loader类加载器加载// 至于cn怎么来的,可以参照next()方法c = Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {fail(service,"Provider " + cn + " not found");}if (!service.isAssignableFrom(c)) {fail(service,"Provider " + cn  + " not a subtype");}try {S p = service.cast(c.newInstance());providers.put(cn, p);return p;} catch (Throwable x) {fail(service,"Provider " + cn + " could not be instantiated",x);}throw new Error();          // This cannot happen
}

到目前为止:ContextClassLoader的作用都是为了破坏Java类加载委托机制,JDBC规范定义了一个JDBC接口,然后使用SPI机制提供的一个叫做ServiceLoader的Java核心API(rt.jar里面提供)用来扫描服务实现类,服务实现者提供的jar,比如MySQL驱动则是放到我们的classpath下面.从上文知道默认线程上下文类加载器就是AppClassLoader,所以例子里面没有显示在调用ServiceLoader前设置线程上下文类加载器为AppClassLoader,ServiceLoader内部则获取当前线程上下文类加载器(这里为AppClassLoader)来加载服务实现者的类,这里加载了classpath下的MySQL的驱动实现.

可以尝试在调用ServiceLoader的load方法前设置线程上下文类加载器为ExtClassLoader,代码如下:

Thread.currentThread().setContextClassLoader(ContextClassLoaderTest.class.getClassLoader().getParent());

然后运行本例子,设置后ServiceLoader内部则获取当前线程上下文类加载器为ExtClassLoader,然后会尝试使用ExtClassLoader去查找JDBC驱动实现,而ExtClassLoader扫描类的路径为:JAVA_HOME/jre/lib/ext/,而这下面没有驱动实现的Jar,所以不会查找到驱动.

总结下,当父类加载器需要加载子类加载器中的资源时,可以通过设置和获取线程上下文类加载器来实现.

java类加载器—ContextClassLoader类加载器相关推荐

  1. 利用classloader同一个项目中加载另一个同名的类_线程上下文类加载器ContextClassLoader内存泄漏隐患...

    前提 今天(2020-01-18)在编写Netty相关代码的时候,从Netty源码中的ThreadDeathWatcher和GlobalEventExecutor追溯到两个和线程上下文类加载器Cont ...

  2. java类加载器_类加载器ClassLoader

    上篇文章说到,Class类可以通过一个类的全限定名去加载类,那么底层是如何去加载的呢?这就是我们今天要聊的类加载器ClassLoader,其可以通过一个类的全限定名来获取描述此类的二进制字节流,也即是 ...

  3. Java类加载器(一)——类加载器层次与模型

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

  4. Java虚拟机10:类加载器

    类与类加载器 虚拟机设计团队把类加载阶段张的"通过一个类的全限定名来获取此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这 ...

  5. Java类加载器 以及类加载器的委托模型

    我们知道 我们在Java中用到的所有的类都是通过类加载器ClassLoader加载到JVM中的,我们还知道  类加载器 也对应着一个类 ,既然这样那么我们会想 那么ClassLoader类是由谁加载的 ...

  6. java赋值语句_深度分析:面试阿里,字节99%会被问到Java类加载机制和类加载器...

    1. 类加载机制 所谓类加载机制就是JVM虚拟机把Class文件加载到内存,并对数据进行校验,转换解析和初始化,形成虚拟机可以直接使用的Jav类型,即Java.lang.Class. 2. 类加载的过 ...

  7. java引导类加载器_Java类加载器层次结构(一)

    类加载器有一种父/子关系.除了引导类加载器外,每个类加载器都有一个父类加载器.本章通过分析ClassLoader的源码来展示java类加载器的层次结构. 根据规定,类加载器会为它的父类加载器提供一个机 ...

  8. Java类加载器(类加载的流程、三大类加载器BootstrapClassLoader、ExtClassLoader、AppClassLoader)

    一.三大类加载介绍 1.1.BootstrapClassLoader BootstrapClassLoader是顶级加载器,默认加载的是%JAVA_HOME%中lib下的jar包和class类文件,他 ...

  9. 面试干货4——你对Java类加载器(自定义类加载器)有了解吗?

    类加载器 推荐:在准备面试的同学可以看看这个系列 一.类加载器的作用 二.Java虚拟机类加载器结构 1. 引导类(启动类)加载器 2. 扩展类加载器 3. 系统类加载器 三.类加载器的加载机制 1. ...

最新文章

  1. 非常实用的Windows7进阶功能
  2. 【机器学习基础】前置知识(二):30分钟掌握常用Jupyter Notebook用法
  3. 81. Search in Rotated Sorted Array II
  4. python3字节转化字符_浅谈 Python3 中对二进制数据 XOR 编码的正确姿势
  5. centos内核错误_centos 升级内核失败回救
  6. ubuntu12.04循环登录,无法进桌面的问题。
  7. php 封装模式,PHP设计模式(三):封装
  8. 阿里云Linux服务器Tomcat9.0的安装及配置
  9. dls 深度受限搜索java_JAVA深入学习(栈和队列)之栈
  10. 最近游戏更新 未整理 无图片 续3
  11. win10如何设置计算机网络访问,win10系统设置允许或拒绝从网络中访问本地电脑的操作方法...
  12. N-gage QD等S60 V1.2机型C盘减肥80K的办法(超越3600KB)
  13. 如何使用商品历史价格查询网站
  14. PT100三线制恒流源接法
  15. 主流配置中心的比较 Spring Cloud Config、Apollo、Nacos
  16. Oracle归档日志使用情况及自动清理
  17. Nachos之系统调用
  18. (P13)元组:戴上了枷锁的列表
  19. goldengate静默安装方法
  20. NLP算法-关键词提取之Jieba算法库

热门文章

  1. win10计算器_这不是Mac,这竟是win10
  2. 【气象】一键式发布预警信息,关键时刻GIS显身手
  3. 全球及中国润滑油市场调查及投资发展规模预测报告2022-2028年版
  4. Unity 输入法回车确定搜索 InputField.onSubmit InputField.onEndEdit
  5. 【水汐のC#】计一个Windows应用程序,在该程序中定义一个学生类和班级类,以处理学生的学号,姓名,语文,数学和英语3门课程的期末考试成绩。实现如下要求的功能:
  6. 批量修改World和PPT的文字格式、颜色、大小等
  7. 为什么小姐姐能摇一晚上不倒?
  8. 入职外包公司一年,人废了
  9. 阿里云 天池学习python(上)
  10. PHP跨境电商商城系统源码支持多语言功能