Java技术专题-JVM研究系列(35)认识一下线程上下文类加载器实现【逆向加载机制】
前提概要
-
线程上下文类加载,就是当前线程所拥有的类加载器,可通过
Thread.currentThread()
获取当前线程。 -
线程上下文类加载器(Thread Context ClassLoader)可以通过java.lang.Thread类的setContextClassLoader()方法设置,创建线程时候未指定的话,则默认从父线程中继承(系统类加载器)。
-
main方法的主线程上下文类加载器就是sun.misc.Launcher$AppClassLoader。
之前讲述过了类加载器的双亲委派模型,该模型的实现是通过类加载器中的parent属性(父加载器)来完成的,默认统一交给最上层启动类加载器去尝试加载。
但是如果希望不遵循双亲委托模型类加载机制,则除了大家公认知道的自定义类加载器并覆盖ClassLoader的loadClass()方法。还有另外一个办法就是采用线程上下文类加载器去实现调整双亲委托机制实现逆向加载机制
public class ThreadClassLoaderTest {
public static void main(String[] agrs) throws ClassNotFoundException {
ClassLoader loader = ThreadClassLoaderTest.class.getClassLoader();
System.out.println(loader); //默认是应用类加载器
//此时获得上下文类加载器:
ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
System.out.println(loader2);//默认也是应用类加载器
//设置为自定义类加载器:
Thread.currentThread().setContextClassLoader(
new ClassLoaderTest("d:/"));
System.out.println(Thread.currentThread().getContextClassLoader());
//使用自定义类加载器加载:
Class c = Thread.currentThread().getContextClassLoader().loadClass("HelloWorld");
System.out.println(c.getClassLoader());//线程上下文类加载器
ClassLoader loader3 = String.class.getClassLoader();
System.out.println(loader3);//启动类加载器 = null
}
}
测试结果:
sun.misc.Launcher$AppClassLoader@41dee0d7
sun.misc.Launcher$AppClassLoader@41dee0d7
ClassLoaderTest@516a4aef
ClassLoaderTest@516a4aef
null
Java--SPI机制
在介绍线程上下文类加载前,我们先了解下Java的SPI机制,因为SPI就是我们线程上下文处理的一个典型案例,线程上下文类加载实现。我们以JDBC的SPI机制做实际案例。
JDBC加载案例分析
举个简单的例子,mysql是如何获取数据库连接:
public class JDBCTest {
public static void main(String[] agrs) { try { // 注册驱动类 Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/testdb"; //通过java库获取数据库连接 Connection conn = java.sql.DriverManager.getConnection(url, "root", "123456"); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } }
以上就是mysql注册驱动及获取connection的过程。在该流程中,java通过线程上线文类加载器实现了逆向类加载。
- 通过系统类加载器,加载Driver类,Class.forName("com.mysql.jdbc.Driver");
- 底层具体实现:registerDriver() 将driver实例注册到java.sql.DriverManager类中,其实就是将com.mysql.jdbc.Driver添加到java.sql.DriverManager类的静态变量CopyOnWriteArrayList集合中。
com.mysql.jdbc.Driver包中:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
- 通过java.sql.DriverManager注册数据库驱动。首先,来看下DriverManager的静态方法。需要明确的是java.sql.DriverManager位于rt.jar包目录下,该目录下的所有类均由Bootstrap启动类加载器进行加载。
java.sql.DriverManager包中:
static {
//初始化Driver驱动实现类:
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
//通过spi来加载jdbc驱动实现类:
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//通过SPI方式,读取META-INF/services下文件中的类:
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
- spi具体实现:
在下面代码中,通过SPI方式来完成java.sql.Driver接口实现类的类加载操作。
java.sql.DriverManager包中:
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//通过SPI方式,读取META-INF/services下文件中的类名:
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {}
return null;
}
});
获取到ServiceLoader对象后,进行遍历操作,遍历出所有META-INF/services文件夹下的实现类名称,之后再进行Class.forName("")类加载操作。类加载操作在driversIterator.next()中完成。
java.util.ServiceLoader包中:
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取线程上下文类加载器:
ClassLoader cl = Thread.currentThread().getContextClassLoader();
//生成ServiceLoader对象:
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = svc;
loader = cl;
reload();
}
在获取ServiceLoader对象时,获取了此时线程上下文中的类加载器,将此类加载赋值给ServiceLoader类中的loader成员变量。在后续类加载过程中,都是使用的此类加载来完成。这一步的操作,直接打破了双亲委派模型,实现了逆向类加载。
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {}
driversIterator.next()方法内部会调用 Class c = Class.forName(cn, false, loader)方法进行类加载操作。而此时传递的loader就是之前获取的线程上下文类加载器,传递的cn就是META-INF/services文件中的具体实现类。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
不再 iPhone 独占,苹果 macOS Monterey 和 iPadOS 15 新增低电量模式
IT之家6 月 8 日消息苹果今天推出的 macOS 12 Monterey 和 iPadOS 15 更新将 iPhone 上的一个关键功能带到了 Mac 和 iPad 上 —— 低电量模式。 根据苹果的说法,Mac 上的低功耗模式会降低系统处理器速度和显示亮度,以进一步延长设备的电池续航。这意味着,如果用户在处理不太繁重的任务,如看视频或浏览网页,能有效增加 Mac 的续航时间。 IT之家了解到,Mac 方面,低电量模式兼容 MacBook(2016 年初及以后)和 MacBook Pro(2016 年初及以后)机型。 在苹果的 iPadOS 15 官方介绍页面上没有提到低电量模式,但在安装更新后也会增加这个低电量模式,低电量模式将暂时降低如下载和邮件抓取等后台活动,直至 iPad 完全充电。
-
下一篇
敏捷史话(十):我牺牲了滑雪时间,参加了一场软件革命——Jon Kern
本文转自敏捷开发。 “在镜头定格的一刹那,所有美好都和你不期而遇”,这是 Jon Kern 对生活的表达。为了更好地记录生活,他在一家名为 flickr 的网站上创建了一个属于自己的照片博客,在这个博客里,Jon 上传了各种随手拍下的照片,拍摄的对象可能是一艘满载的渡轮,可能是一对长得像警卫的消防栓,也可能是倒映在水面的一只蜥蜴……Jon 不仅在生活中习惯于观察、欣赏身边的小细节,同样在工作中也习惯于从细节入手,推动业务成功。 忽略他身上的耀眼光环,你会重新认识 Jon Kern。 一、“初识软件开发” 20世纪60年代末,“软件危机”出现之后,人们开始思考:如何满足不断增长的需求,以及如何维护数量不断膨胀的软件产品。这之后的几十年间,快速原型、增量等模型不断涌现,推动软件行业不断向前发展。也正是在这一激烈动荡的时期中,Jon Kern 发现了一个未知的世界,带着对这个世界的好奇,他开始踏足软件开发领域。 1981年,Jon 顺利地从俄亥俄州立大学毕业,获得了航空工程学士学位。带着初入社会的兴奋与激情,他以一名项目工程师的身份进入海军航空推进中心工作,在推进中心,Jon 的工作内容...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS关闭SELinux安全模块
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS8编译安装MySQL8.0.19