【Java技术探索】认识一下线程上下文类加载器实现【逆向加载机制】

前提概要

  • 线程上下文类加载,就是当前线程所拥有的类加载器,可通过 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通过线程上线文类加载器实现了逆向类加载

  1. 通过系统类加载器,加载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!");
        }
    }
}
  1. 通过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);
        }
    }
}
  1. 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文件中的具体实现类

上一篇:Java使用JDBC DriverManager 接口连接MySQL数据库


下一篇:java-JDBC