JVM深层系列「逆向ClassLoader加载机制」认识一下线程上下文类加载器实现
Posted 浩宇の天尚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM深层系列「逆向ClassLoader加载机制」认识一下线程上下文类加载器实现相关的知识,希望对你有一定的参考价值。
前提概要
-
线程上下文类加载,就是当前线程所拥有的类加载器,可通过
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文件中的具体实现类。
以上是关于JVM深层系列「逆向ClassLoader加载机制」认识一下线程上下文类加载器实现的主要内容,如果未能解决你的问题,请参考以下文章
Android 逆向类加载器 ClassLoader ( 启动类加载器 | 扩展类加载器 | 应用类加载器 | 类加载的双亲委托机制 )
JVM理解(上):classloader加载class文件的原理和机制
Android 逆向Dalvik 函数抽取加壳 ( 类加载流程分析 | ClassLoader#loadClass 函数分析 | BaseDexClassLoader#findClass 分析 )(代