线程上下文类加载器的分析
双亲委托模型的弊端
- 我们先看一段我们非常熟悉的数据库连接相关的代码片段。
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/RUNOOB","root","123456");
Statement stmt = conn.createStatement();
- 如下图分析
案例分析
-
在上述图中的第五步为什么会用线程上下文加载器进行加载呢?
-
在双亲委托模型(类加载器深入理解和双亲委托模型的案例分析 )的机制下,类的加载是由下而上的。即下次的加载器会委托上层进行加载。有些接口是Java核心库(rt.jar)提供的例如上面的createStatement接口,而Java核心库是由启动类加载器进行加载的。而这些接口的具体实现是来自不同的厂商(Mysql)。而具体的实现都是通过依赖jar包放到我们项目中的classPath下的。Java的启动类加载器/根类加载器是不会加载这些其他来源的jar包。
-
我们都知道classPath下的jar包是有我们系统类加载器/应用加载器进行加载,根据我们双亲委托的机制父类加载器是看不到子类(系统类加载器)所加载的具体实现。createStatement 这个接口是由根类加载器进行加载 而具体的实现是又加载不了。在双亲委托的机制下,createStatement这个接口就无具体的实现。
-
我们Java的开发者就通过给当前线程池设置上下文加载器的机制,就可以由设置的上下文加载器来实现对于接口实现类的加载。换句话说父类加载器可以使用当前线程上下文加载器加载父类加载器加载不了的一些接口的实现。完美了解决了由于SPI模型(接口定义在核心库中,而实现由各自的厂商以jar的形式依赖到我们项目中)的接口调用。
定义
- 如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程继承父线程的上下文加载器,Java应用运行时的初始线程上下文的加载器就是系统类加载器。在线程中运行的代码可以通过该类加载器进行加载类和资源。
- 对上述的结果肯定有人怀有疑问?那我们怎么进行验证呢?
- 我们都知道sun.misc.Launcher类是java的入口,在启动java应用的时候会首先创建Launcher类,创建Launcher类的时候回准备应用程序运行中需要的类加载器。
- 接下来我们看一下Launcher的构造方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 这里是获取appClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// Thread.currentThread()就是创建我们Launcher对象的线程即主线程
// appClassLoader设置成主线程的上下文加载器
Thread.currentThread().setContextClassLoader(this.loader);
...... 忽略下面的代码
}
使用场景
- 场景: 当高层(这里的高底层是指双亲委派模型的层次)提供了统一的接口让底层去实现。同时又要在高层加载底层的类时,就必须通过线程上下文加载器帮忙高层的ClassLoader进行加载该类。
来源:CSDN
作者:小鱼儿-karl
链接:https://blog.csdn.net/qq_33249725/article/details/104865055