ContextClassLoader详解
ContextClassLoader是通过Thread.currentThread().getContextClassLoader()返回该线程上下文的ClassLoader1、前置知识在讲解ContextClassLoader之前,需要先提两个知识点:1)双亲委派模型启动类加载器(Bootstrap ClassLoader):负责将放在<JAVA HOME>\lib目录中的,或者被-
ContextClassLoader是通过Thread.currentThread().getContextClassLoader()
返回该线程上下文的ClassLoader
1、前置知识
在讲解ContextClassLoader之前,需要先提两个知识点:
1)双亲委派模型
- 启动类加载器(Bootstrap ClassLoader):负责将放在
<JAVA HOME>\lib
目录中的,或者被-Xbootclasspath
参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那直接使用null代替即可 - 扩展类加载器(ExtClassLoader):由
sun.misc.Launcher$ExtClassLoader
实现,它负责加载<JAVA HOME>\lib\ext
目录中的,或者被java.ext.dirs
系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器 - 应用程序类加载器(AppClassLoader):由
sun.misc.Launcher$AppClassLoader
实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()
方法的返回值,所以一般也称它为系统类加载。它负责加载用户类路径(ClassPath)上所有指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器
类加载之间的这种层次关系,称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承的关系来实现,而是都使用组合关系来复用父加载器的代码
双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object
,它存放在rt.jar
之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类
2)如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载
比如Spring作为一个Bean工厂,它需要创建业务类的实例,并且在创建业务类实例之前需要加载这些类。Spring是通过调用Class.forName
来加载业务类的。调用Class.forName()
的时候,会获取调用该方法的类的类加载器,使用该类加载器来加载Class.forName()
中传入的类,代码如下:
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
// 获取调用该方法的类
Class<?> caller = Reflection.getCallerClass();
// ClassLoader.getClassLoader获取调用该方法的类的类加载器
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
2、为什么需要ContextClassLoader?
当我们需要加载一个类,从自定义ClassLoader,到AppClassLoader,再到ExtClassLoader,最后到Bootstrap ClassLoader。没问题, 很顺利。这是从下到上加载。但是反过来,当从上到下加载的时候,这个变得是一个不可能完成的任务。为了弥补这个缺陷, 特定设计的ContextClassLoader
这里你可能会有个疑问:为什么会出现从上到下加载的情况。比如一个类是由Bootstrap ClassLoader加载,该类引用了一个我们自己开发的类(该类能被AppClassLoader加载但不能被Bootstrap ClassLoader加载),由如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载可知:默认情况下我们自己开发的类会被Bootstrap ClassLoader尝试加载,最终会由于无法加载到类而抛出异常
以SPI为例,SPI接口属于Java核心库,由BootstrapClassLoader加载,当SPI接口想要引用第三方实现类的具体方法时,BootstrapClassLoader无法加载Classpath下的第三方实现类,这时就需要使用线程上下文类加载器ContextClassLoader来解决。借助这种机制可以打破双亲委托机制限制
SPI核心类ServiceLoader源码如下:
public final class ServiceLoader<S>
implements Iterable<S>
{
public static <S> ServiceLoader<S> load(Class<S> service) {
// 线程上下文类加载器,在Launcher类的构造器中被赋值为AppClassLoader,它可以读到ClassPath下的自定义类
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
3、ContextClassLoader默认为AppClassLoader
JVM启动时,会去调用Launcher类的构造方法:
public class Launcher {
public Launcher() {
ClassLoader extcl;
try {
// 首先创建扩展类加载器
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader");
}
// Now create the class loader to use to launch the application
try {
// 再创建AppClassLoader并把extcl作为父加载器传递给AppClassLoader
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
}
// 设置线程上下文类加载器,稍后分析
Thread.currentThread().setContextClassLoader(loader);
// 省略其他代码...
}
Launcher初始化时首先会创建ExtClassLoader类加载器,然后再创建AppClassLoader并把ExtClassLoader传递给它作为父类加载器,还把AppClassLoader默认设置为线程上下文类加载器
4、子线程ContextClassLoader默认为父线程的ContextClassLoader
Thread在init()
方法中会把子线程ContextClassLoader设置为父线程的ContextClassLoader
public
class Thread implements Runnable {
private ClassLoader contextClassLoader;
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 省略其他代码...
// 当前线程为父线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
// 子线程ContextClassLoader设置为父线程的ContextClassLoader
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
// 省略其他代码...
}
参考:
https://zhuanlan.zhihu.com/p/58793441
https://blog.csdn.net/a1240466196/article/details/105375410
更多推荐
所有评论(0)