专栏首页博岩Java大讲堂Java虚拟机--线程上下文类加载器

Java虚拟机--线程上下文类加载器

线程上下文类加载器

通过名字可知,线程上下文类加载,就是当前线程所拥有的类加载器,可通过Thread.currentThread()获取当前线程。

线程上下文类加载器(Thread Context ClassLoader)可以通过java.lang.Thread类的setContextClassLoader()方法设置,创建线程时候未指定的话,则默认从父线程中继承。

那父线程中也没指定呢?那么会默认为应用程序的类加载器。例如:main方法的线程上下文类加载器就是sun.misc.Launcher$AppClassLoader。

前两篇文章中,我们讲解了类加载器的双亲委派模型,该模型的实现是通过类加载器中的parent属性(父加载器)来完成的,默认统一交给最上层类加载器去尝试加载。

那,这个线程上下文类加载器又是干啥的?

在介绍线程上下文类加载前,我们先了解下Java的SPI机制。

Java--SPI机制

线程上下文类加载实现

public class JVMTest6 {

   public static void main(String[] agrs) throws ClassNotFoundException {
       ClassLoader loader = JVMTest6.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

JDBC加载案例分析

介绍完了spi,下来我们来举几个例子进一步说明逆向类加载。

举个简单的例子,mysql是如何获取数据库连接:

public class JVMTest7 {

   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!");
        }
    }
}

(2)通过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);
        }
    }
}

(3)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) {}

通过debug发现,driversIterator.next()方法内部会调用Class c = Class.forName(cn, false, loader)方法进行类加载操作。而此时传递的loader就是之前获取的线程上下文类加载器,传递的cn就是META-INF/services文件中的具体实现类。

由于笔者是通过本地的test进行测试,所以上文中涉及到的类加载器都是AppClassLoader系统类加载器。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java虚拟机--类加载器如何加载一个Class文件

    贾博岩
  • Java虚拟机--自定义类加载器

    贾博岩
  • Java虚拟机--类加载器源码

    贾博岩
  • 深入探索“线程上下文类加载器”

    Q: 越基础的类由越上层的加载器进行加载,如果基础类又要调用回用户的代码,那该怎么办? A: 解决方案:使用“线程上下文类加载器”

    tomas家的小拨浪鼓
  • 深入理解图片和框架的原生懒加载功能

    看到本文标题你会问「懒加载是什么东西?」CSS-Tricks 网站中有非常多的探讨懒加载的文章,其中有一篇非常详尽的《用 JavaScript 花式实现懒加载的...

    ConardLi
  • 类加载器知识点吐血整理

    我们平时写的代码或程序到底是如何运行起来的呢? 比如我开发用的是 java 语言,源码是是 .java 的文件,但他们是没有办法运行的。通常我们会打成 jar ...

    每天晒白牙
  • java类的加载过程和类加载器的分析

    我们知道,我们写的java代码保存的格式是 .java, java文件被编译后会转换为字节码,字节码可以在任何平台通过java虚拟机来运行,这也是java能够跨...

    用户2141593
  • 揭秘java中无数人伤透脑筋最为神秘的技术之一——ClassLoader

    ClassLoader 是 Java 届最为神秘的技术之一,无数人被它伤透了脑筋,摸不清门道究竟在哪里。网上的文章也是一篇又一篇,经过本人的亲自鉴定,很多都是在...

    java架构师
  • 第一章 类加载到卸载的全过程分析

     在Java代码中,类型的加、连接与初始化过程都是在程序运行期间完成的。其中类型指我们定义的一个class、interface、enum,此时并未包含对象。这一...

    Fisherman渔夫
  • java类的加载过程和类加载器的分析

    我们知道,我们写的java代码保存的格式是 .java, java文件被编译后会转换为字节码,字节码可以在任何平台通过java虚拟机来运行,这也是java能够跨...

    矿泉水

扫码关注云+社区

领取腾讯云代金券