美文网首页我爱编程编程语言-Java系列JVM
Java虚拟机--线程上下文类加载器

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

作者: 贾博岩 | 来源:发表于2018-05-26 10:57 被阅读283次

    线程上下文类加载器

    通过名字可知,线程上下文类加载,就是当前线程所拥有的类加载器,可通过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系统类加载器。

    相关文章

      网友评论

        本文标题:Java虚拟机--线程上下文类加载器

        本文链接:https://www.haomeiwen.com/subject/tixmjftx.html