title: 类加载机制(七):线程上下文类加载器
date: 2019-03-21 20:14:06
categories:
- Java虚拟机
tags: - 类加载机制
- 线程上下文类加载器
双亲委托机制的破坏
我们知道,class
文件的加载是按照双亲委托机制完成的,这个机制解决了各个类加载器的基础类的统一问题,因为上层类加载器加载的类对下层加载的类是可见的,所以这些基础类可以被Java
程序所调用,但是如果这些基础类需要调用用户所写的类呢,可下层类加载器加载的类不是对上层类加载器加载的类是透明的吗?这种情况不是不可能的,比如JDBC
,它位于rt.jar
包下,是由启动类加载器加载,而它却需要classPath
下的接口提供者(Service Provider Interface)的代码,但是启动类不可能去加载这些代码,那怎么办呢?
SPI(Service Provider Interface)
:
只提供了接口的声明,具体实现由厂商完成。某些SPI
需要调用由厂商实现并部署在classPath
下的接口实现代码。这些接口由启动类加载器去加载,但启动类加载器不认识classPath
下的代码。
这时候就需要对双亲委托机制进行“破坏”了,Java
设计者设计了一种叫做“线程上下文类加载器”的机制,当这些接口需要实现的代码时,就去使用这个线程上下文类加载器完成这些接口实现代码的加载。
线程上下文类加载器
线程上下文类加载器contextClassLoader
,其实,我们在分析Launcher
类的源码时,已经遇到过了:
Thread.currentThread().setContextClassLoader(this.loader);
在Launcher
类的构造方法中,执行了这条语句,并将系统类加载器传进去,我们跟着去看看Thread
的源码。
private ClassLoader contextClassLoader;
内部维护了一个类加载器,也就是线程上下文类加载器。
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
在Thread
的init
方法中,线程继承其父线程的上下文类加载器。
public void setContextClassLoader(ClassLoader cl) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
contextClassLoader = cl;
}
在Thread
中,可以设置线程类加载器,也就是说,若如果没有通过setContextClassLoader
进行设置的话,线程将继承其父线程的上下文类加载器。前面说过,在Launcher
类中设置了线程上下文类加载为系统类加载器,即在Java
程序中未设置线程上下文类加载器的话,线程上下文类加载器就为系统类加载器。
深入理解线程上下文类加载器
线程上下文类加载器的一般使用模式
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try{
Thread.currentThread().setContextClassLoader(targetccl);
doMethod();
}finally{
Thread.currentThread().setContextClassLoader(classLoader);
}
线程上下文类加载器的一般使用模式(获取-使用-还原)
在
doMethod()
里面调用了Thread.currentThread().setContextClassLoader()
来获取当前线程的上下文类加载器做某些事情。
ServiceLoader类
在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托上层进行加载。但是对于SPI
来说。有些接口是Java
核心库所提供的,而Java
核心库是由启动类加载器来加载的,而这些接口的实现却是来自于不同的jar
包(由各独立厂商实现),Java
的启动类加载器是不会加载其他来源的jar
包,这样就导致一些接口由启动类加载加载,实现由其他加载器加载,传统的双亲委托机制就会无法满足SPI
的要求。而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对于接口实现类的加载 。就比如JDBC
驱动,如下图所示。
首先,在pom
文件中添加了关于MySql
的依赖,我们通过代码找到项目中的驱动。
public class MyTest17 {
public static void main(String[] args){
//通过ServiceLoader去加载驱动
ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class