大数据平台底层技术-JAVA篇-如何动态加载不同版本的 HIVE JDBC 驱动 - 一文读懂JAVA的类加载机制...

1 前言

大家好,我是明哥!

市场上大数据相关岗位和对应的的技能,主要分为三大类:

  • 侧重数据平台底层技术:如大数据运维工程师,大数据开发工程师,大数据架构师;

  • 侧重数据的加工和分析:如数据仓库工程师/建模工程师,数据工程师/ETL开发,数据分析师;(data engineer, data analyst)

  • 侧重数据的深度挖掘:如算法工程师,数据科学家,AI工程师;(data scientist)

7cbebf3ebf541abdc72e2d5403c0ce6b.png
大数据岗位一览

其中第一大类,“侧重数据平台底层技术”,需要在底层大数据平台(如CDH/HDP/CDP/TDH等)的基础上,开发一些平台类的工具或应用,来支撑或辅助第二大类和第三大类相关从业人员的日常工作,所以需要扎实的 JAVA 或 SCALA 语言功底,也需要扎实的 LINUX 运维知识。

有鉴于此,后续笔者会推出系列文章,从大数据的角度出发,专门讲述数据平台底层技术 (分为 JAVA 篇与 LINUX 篇)。

本文是“数据平台底层技术-JAVA篇” 之一,讲述如何根据用户的具体情况,动态加载特定目录下的 JAVA 类,这在一些跨平台跨版本的大数据应用中,是一个常见的需求。(比如根据用户的具体情况,加载特定目录下特定版本的 HIVE JDBC 驱动),背后涉及到的知识是 JAVA 的类加载机制。

2 什么是类加载器

  • 类加载器,即 Class loaders, 是 JRE 的一部分,负责在 JVM 程序运行时,动态加载 JAVA 类到 JVM 内存中;

  • 通过类加载器,JVM 在运行 JAVA 程序时,不需要知道底层的文件系统和文件,即类加载器解耦了 JVM 和操作系统底层的文件系统;

  • 我们之所以说类是“动态”加载的,主要是因为运行程序时需要的所有的 JAVA 类,并不是一次性全部加载到 JVM 内存中,而是在需要哪个类时才加载那个类。

3 JRE 中主要有哪些内置的类加载器

3.1. Bootstrap Class Loader

  • bootstrap class loader 是 JVM 核心的一部分,是使用 native 代码编写的,所以在不同的平台上(如windows,linus,macos),有不同的实现;

  • Bootstrap class loader 是所有其它类加载器的父类;( 这里所说的父类,不是普通类继承关系层面上的父类);

  • Bootstrap Class Loader 负载加载 JDK 内部类,这些内部类在目录 $JAVA_HOME/jre/lib 下,比如 rt.jar;

  • Bootstrap Class Loader 打印时会显示为 null (因为其是用 native 代码实现的);

3.2 Extension Class Loader

  • extension class loader 是 bootstrap class loader 的子类;

  • extension class loader 负责加载JDK的扩展类,这些扩展类一般在目录 $JAVA_HOME/lib/ext 下,或系统变量 java.ext.dirs 指定的其它目录;

3.3. System/application Class Loader

  • system class loader, 或称为 application class loader, 是 Extensions classloader 的子类;

  • system class loader 负责加载 JAVA 应用级别的类, 这些类都在环境变量 classpath 指定的目录下,用户在提交 JAVA 程序时,可以通过命令行参数 -classpath 或 -cp 指定环境变量 classpath。

大家可以通过以下示例代码进行验证:

public void printClassLoaders() throws ClassNotFoundException {

    System.out.println("Classloader of this class:"
        + PrintClassLoader.class.getClassLoader());

    System.out.println("Classloader of Logging:"
        + Logging.class.getClassLoader());

    System.out.println("Classloader of ArrayList:"
        + ArrayList.class.getClassLoader());
}

示例输出:
Class loader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
Class loader of Logging:sun.misc.Launcher$ExtClassLoader@18769467
Class loader of ArrayList:null

4 JRE 中类加载器是怎么工作的

e8806671acdf20f8e79daafbde95d8c2.png

双亲委派模型

类加载器加载类,遵循了双亲委派模型:

  • “双亲委派模型”的双亲,更严谨来讲,其实是多级单亲;

  • 双亲委派模型下,所有的类加载器,除了 bootstrap class loader, 都有一个唯一的父加载器;

  • ”双亲委派模型“的父子关系,不是普通 JAVA 类关系层面上的父子继承关系 (extends),而更像是一种有层次之分的组合关系;

  • 当类加载器收到一个类或资源的加载请求后,会首先搜索该类或资源是否已经被加载到内存中了,如果已经被加载了的话,不会重复加载该类或资源;如果没有被加载,则会将加载请求委派给父级加载器去加载;(在内存中搜索时,一样会遵循类的可见性,类的可见性的具体含义见下文)

  • 父级加载器一样遵循向上委派机制,将加载请求委派给其父级加载器去加载,通过这种向上传导关系,所有的类加载请求,最终都会被传入到启动类加载器(Bootstrap ClassLoader)中;

  • 只有当父级加载器反馈无法完成特定类或资源的加载请求时,即父级加载器在它的搜索范围内找不到该类或资源时,子级加载器才尝试在自己的搜索范围内加载类或资源;

  • 如果所有的父级加载器在其搜索范围内都找不到该类或资源,且子级加载器在自己的搜索范围内也找不到该类或资源,JVM 就会报 java.lang.ClassNotFoundException;

遵循以上双亲委派模型,当我们需要加载一个应用类到 JVM 中时:

  • system class loader 首先搜索 jvm 内存,发现该类没有被加载过,然后会委派该加载请求给其父类加载器 extension class loader;

  • extension class loader 又会将该加载请求委派给其父类 bootstrap class loader;

  • bootstrap class loader 尝试加载该类,会在其搜索范围内(即目录 $JAVA_HOME/jre/lib)尝试查找该类,找不到;

  • 然后 extension class loader 才会尝试加载该类,在其搜索范围内(即目录 $JAVA_HOME/lib/ext 下,或系统变量 java.ext.dirs 指定的其它目录下)尝试查找该类,找不到;

  • 然后 system class loader 才会尝试自己加载该类,会在其搜索范围内(环境变量 classpath 指定的目录下)尝试查找该类,查找成功,加载成功。

通过以上双亲委派模型,JVM 确保了:

  • 类的唯一性 uniqueness:只有在父类加载器在其搜索范围内找不到类,无法加载类时,子类加载器才会尝试自己加载,这样就避免了重复加载,确保了类的唯一性:比如类 java.lang.Object 在 $JAVA_HOME/jre/lib/rt.jar中,无论哪个类加载器需要加载该类,最终都是委派给处于模型顶端的启动类加载器bootstrap class loader 加载的,而 bootstrap class loader 只会在$JAVA_HOME/jre/lib/rt.jar 中搜索加载该类,因此类 java.lang.Object 在各种加载环境中都是同一个类;

  • 类的可见性 Visibility:父类加载器加载的类对子类加载器(加载的类)可见,而子类加载器加载的类对父类加载器(加载的类)不可见:(所谓的可见,即能访问):即 system class loader 加载的类,可见 extension 和 Bootstrap class loaders 加载的类,反过来则不可见;比如,Class A 是 application class loader 加载的,而 class B 是 extensions class loader 加载的, 则此时 Application class loader 加载的其它类,可以访问 A 和 B; 而 extenson class loader 加载的其它:类, 则只可以访问 B 而不能访问A;

  • 类的安全性 Security:双亲委派模型实现了类的唯一性和可见性,避免了用户随意定义类加载器加载核心 API 带来的安全隐患,从而进一步确保了类的安全性;

以上双亲委派模型的具体实现,可以参见源码java.lang.ClassLoaderf27efad51ec3f2f476d4e3849e32668d.png

5. JRE 中双亲委派模型的不足与 thread Context Classloaders

有时,JVM 核心类可能需要动态加载应用开发人员提供的特定类或资源:

  • 比如 JNDI 中核心功能都是在 rt.jar 的核心内部类中实现的,但在程序运行时,这些 JNDI 类可能需要加载类加载路径下各个 vendor 的具体实现类 (通过命令行参数 -classpath 或 -cp 指定类加载路径的具体目录),此时就需要 bootstrap class loader (parent class loader) 加载本应只对 application class loader 可见的类 (child class loader);

  • 再比如 JDBC 中核心功能都是在 rt.jar 的核心内部类中实现的,包括 java.sql.DriverManager/java.sql.Driver等, 但在程序运行时,这些 JDBC 类可能需要加载类加载路径下各个 vendor 的具体实现类 (通过命令行参数 -classpath 或 -cp 指定类加载路径的具体目录),比如 oracle jdbc 驱动 oracle.jdbc.driver.OracleDriver,此时也需要 bootstrap class loader (parent class loader) 加载本应只对 application class loader 可见的类 (child class loader).

此时,以上 J2SE 双亲委派机制就不 work 了。为此,JVM 提供了 thread context class loader:

  • 每个线程在创建时,都有一个对应的 ContextClassLoader,来负责该线程中类或资源的加载;

  • 当没有显示指定时,线程的 ContextClassLoader 跟其父线程的 context class loader 是同一个;

  • 可以通过方法 getContextClassLoader() 获得当前线程的 ContextClassLoader;

  • 可以通过方法 setContextClassLoader(ClassLoader cl) 重置当前线程的 ContextClassLoader;

691ed73e8ab1d4b812c729e2e5782b19.png

java.lang.Thread#getContextClassLoader

7df2299f3d523cf586a65159908212e0.png

java.lang.Thread#setContextClassLoader

ac59b02c7797c2a8ccaaf095a5e6529a.png

java.sql.DriverManager#getConnection(java.lang.String, java.util.Properties, java.lang.Class<?>)

可以通过以下代码,验证 JDBC 相关类的加载器:

Class<?> driverManagerClass = Class.forName("java.sql.DriverManager");
Class<?> driverClass = Class.forName("java.sql.Driver");
Class<?> oracleDriverClass = Class.forName("oracle.jdbc.driver.OracleDriver");
System.out.println("driverManagerClass.getClassLoader():" + driverManagerClass.getClassLoader());
System.out.println("driverClass.getClassLoader():" + driverClass.getClassLoader());
System.out.println("oracleDriverClass.getClassLoader():" + oracleDriverClass.getClassLoader());
对应的输出如下:
driverManagerClass.getClassLoader():null
drivrClass.getClassLoader():null
oracleDriverClass.getClassLoader():sun.misc.Launcher$AppClassLoader@18b4aac2

6 自定义类加载器

6.1 自定义类加载器的应用场景

通过合理使用上述 JVM 自带的各个类加载器,一般就已经能够满足绝大多情况下类或资源的加载需求了。但在某些特殊情况下,我们仍然需要自定义类加载器,这些场景主要有:

  • 辅助修改已有的字节码 bytecode, 比如 weaving agents;

  • 加载本地磁盘文件系统之外的网络上的类或资源,比如浏览器加载远程web服务器上的 applet;

6.2 如何自定义类加载器

自定义类加载器,只需要继承类 ClassLoader 并复写方法 findClass() 即可。以下示例代码,自定义了一个类加载器,来从指定的文件中加载 byte array 并构建类:

public class CustomClassLoader extends ClassLoader {

    @Override
    public Class findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromFile(name);
        return defineClass(name, b, 0, b.length);
    }

    private byte[] loadClassFromFile(String fileName)  {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
                fileName.replace('.', File.separatorChar) + ".class");
        byte[] buffer;
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        int nextValue = 0;
        try {
            while ( (nextValue = inputStream.read()) != -1 ) {
                byteStream.write(nextValue);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        buffer = byteStream.toByteArray();
        return buffer;
    }
}

7 相关测试类

package com.hundsun;

import org.apache.hadoop.conf.Configuration;
import org.apache.hive.jdbc.HiveDriver;

import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author: liming
 * @date: 2021/8/28 20:12
 * @description:
 */
public class ClassLoaderTest {

    private static String dataSourceDriverPath;
    private static String classPath = "E:\\git\\sparkTest\\target";

    public static void main(String[] args) {
        try {
//        dataSourceDriverPath = args[0];
            dataSourceDriverPath = "D:\\software\\数据源驱动-202101.03.000\\drivers\\hive-cdh6\\lib";
//        System.out.println("驱动路径:" + dataSourceDriverPath);
            System.out.println("ClassLoaderTest.class.getClassLoader():" + ClassLoaderTest.class.getClassLoader().toString());
//        System.out.println("ClassLoaderTest.class.getClassLoader().getParent():" + ClassLoaderTest.class.getClassLoader().getParent().toString());
//        System.out.println("ClassLoaderTest.class.getClassLoader().getParent().getParent():" + ClassLoaderTest.class.getClassLoader().getParent().getParent());
//        System.out.println("ClassLoaderTest.class.getClassLoader().getParent().getParent().getParent():" + ClassLoaderTest.class.getClassLoader().getParent().getParent().getParent());
            jdbcDriverTest();
            getDriverUsingContextClassLoader();
            getDriverUsingUrlClassLoader();
            getClassUsingContextClassLoader(classPath);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.out.println(e.getCause());
            e.printStackTrace();
        }
    }
    public static void jdbcDriverTest(){
        try{
            Class<?> driverManagerClass = Class.forName("java.sql.DriverManager");
            Class<?> driverClass = Class.forName("java.sql.Driver");
            Class<?> oracleDriverClass = Class.forName("oracle.jdbc.driver.OracleDriver");
            System.out.println("driverManagerClass.getClassLoader():" + driverManagerClass.getClassLoader());
            System.out.println("driverClass.getClassLoader():" + driverClass.getClassLoader());
            System.out.println("oracleDriverClass.getClassLoader():" + oracleDriverClass.getClassLoader());
//            Connection con=DriverManager.getConnection("jdbc:oracle:thin:@//10.20.29.239:1521/orclpdb1","hs_cic","hundsun");
            Connection con= DriverManager.getConnection(
                    "jdbc:oracle:thin:@//10.20.23.215:1521/orcl","hs_dap","hundsun");

            System.out.println(con.getAutoCommit());
            con.setAutoCommit(false);
            System.out.println(con.getAutoCommit());
            Statement stmt=con.createStatement();
            ResultSet rs=stmt.executeQuery("select count(1) from HS_DAP.DAP_DATA_TYPE_LIMING");
            while (rs.next()) {
                System.out.println(rs.getInt(1));
//                System.out.println(rs.getString(1) + "  " + rs.getInt(2) + "  " + rs.getString(3));
            }
            PreparedStatement ps = con.prepareStatement("delete from HS_DAP.DAP_DATA_TYPE_LIMING where HIVE_DATA_TYPE = ?");
            ps.setString(1,"DATE");
            ps.execute();
            Thread.sleep(1 * 60 * 1000);
            con.close();
        }catch(Exception e){ System.out.println(e);}
    }
    public static Driver getDriverUsingUrlClassLoader() throws URISyntaxException {
        String driverPath = dataSourceDriverPath;
        List<File> files = scanDir(driverPath);
        System.out.println(files.get(0).toURI());
        Class<?> driver = null;
        Class<?> configuration = null;
        try {
            URL[] urls = new URL[files.size()];
            for (int i = 0; i < files.size(); i++) {
                urls[i] = files.get(i).toURI().toURL();
//                System.out.println(files.get(i).toURI());
            }
            System.out.println("Thread.currentThread().getContextClassLoader():" + Thread.currentThread().getContextClassLoader());
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            System.out.println("urlClassLoader:" + urlClassLoader);
            URL resource = urlClassLoader.getResource("core-site-test.xml");
            String resourceURI = resource.toURI().toString();
            System.out.println("resourceURI:" + resourceURI);
            System.out.println("resource.getPath():" + resource.getPath());
            driver = urlClassLoader.loadClass("org.apache.hive.jdbc.HiveDriver");
            System.out.println("org.apache.hive.jdbc.HiveDriver.class.getClassLoader():" + HiveDriver.class.getClassLoader().toString());
            System.out.println("org.apache.hive.jdbc.HiveDriver.class.getClassLoader().getParent():" + HiveDriver.class.getClassLoader().getParent().toString());

            configuration = urlClassLoader.loadClass("org.apache.hadoop.conf.Configuration");

            System.out.println("org.apache.hadoop.conf.Configuration.class.getClassLoader():" + Configuration.class.getClassLoader().toString());
            System.out.println("org.apache.hadoop.conf.Configuration.class.getClassLoader().getParent():" + Configuration.class.getClassLoader().getParent().toString());
            System.out.println("Thread.currentThread().getContextClassLoader():" + Thread.currentThread().getContextClassLoader());
            System.out.println("urlClassLoader.getParent():" + urlClassLoader.getParent());
            Thread.currentThread().setContextClassLoader(urlClassLoader);
            System.out.println("Thread.currentThread().getContextClassLoader()" + Thread.currentThread().getContextClassLoader());

            return (Driver) driver.newInstance();
        } catch (Exception e) {
            System.out.println(e.getCause());
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

    public static Driver getDriverUsingContextClassLoader() throws URISyntaxException {
        String driverPath = dataSourceDriverPath;
        List<File> files = scanDir(driverPath);
        System.out.println(files.get(0).toURI());
        Class<?> driver = null;
        Class<?> configuration = null;
        try {
            URL[] urls = new URL[files.size()];
            for (int i = 0; i < files.size(); i++) {
                urls[i] = files.get(i).toURI().toURL();
//                System.out.println(files.get(i).toURI());
            }
            ClassLoader prevContextClassLoader = Thread.currentThread().getContextClassLoader();
            System.out.println("prevContextClassLoader:" + prevContextClassLoader);
            // create a URLClassLoader with the thread ContextClassLoader as parent
            URLClassLoader urlContextClassLoader = new URLClassLoader(urls, prevContextClassLoader);
            System.out.println("urlContextClassLoader:" + urlContextClassLoader);
            URL resource = urlContextClassLoader.getResource("core-site-test.xml");
            String resourceURI = resource.toURI().toString();
//            System.out.println("g:" +resourceURI);
            System.out.println("resource.getPath():" + resource.getPath());
            driver = urlContextClassLoader.loadClass("org.apache.hive.jdbc.HiveDriver");
            System.out.println("org.apache.hive.jdbc.HiveDriver.class.getClassLoader():" + org.apache.hive.jdbc.HiveDriver.class.getClassLoader().toString());
            System.out.println("org.apache.hive.jdbc.HiveDriver.class.getClassLoader().getParent():" + org.apache.hive.jdbc.HiveDriver.class.getClassLoader().getParent().toString());

            configuration = urlContextClassLoader.loadClass("org.apache.hadoop.conf.Configuration");
            System.out.println("Thread.currentThread().getContextClassLoader():" + Thread.currentThread().getContextClassLoader());

            System.out.println("org.apache.hadoop.conf.Configuration.class.getClassLoader():" + org.apache.hadoop.conf.Configuration.class.getClassLoader().toString());
            System.out.println("org.apache.hadoop.conf.Configuration.class.getClassLoader().getParent():" + org.apache.hadoop.conf.Configuration.class.getClassLoader().getParent().toString());
            System.out.println("urlContextClassLoader.getParent():" + urlContextClassLoader.getParent());
            // set the thread contextClassLoader to the newly created urlContextClassLoader
            Thread.currentThread().setContextClassLoader(urlContextClassLoader);
            System.out.println("Thread.currentThread().getContextClassLoader()" + Thread.currentThread().getContextClassLoader());
            return (Driver) driver.newInstance();
        } catch (Exception e) {
            System.out.println(e.getCause());
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

    public static void getClassUsingContextClassLoader(String classPath) throws URISyntaxException {
        List<File> files = scanDir(classPath);
        System.out.println(files.get(0).toURI());
        try {
            URL[] urls = new URL[files.size()];
            for (int i = 0; i < files.size(); i++) {
                urls[i] = files.get(i).toURI().toURL();
//                System.out.println(files.get(i).toURI());
            }
            ClassLoader prevContextClassLoader = Thread.currentThread().getContextClassLoader();
            System.out.println("prevContextClassLoader:" + prevContextClassLoader);
            System.out.println("prevContextClassLoader.getParent():" + prevContextClassLoader.getParent());
            System.out.println("prevContextClassLoader.getParent().getParent():" + prevContextClassLoader.getParent().getParent());
            URLClassLoader urlContextClassLoader = new URLClassLoader(urls, prevContextClassLoader);
            System.out.println("urlContextClassLoader:" + urlContextClassLoader);
            System.out.println("ClassLoader.getSystemClassLoader():" + ClassLoader.getSystemClassLoader());

            Thread.currentThread().setContextClassLoader(urlContextClassLoader);
            System.out.println("Thread.currentThread().getContextClassLoader()" + Thread.currentThread().getContextClassLoader());
            System.out.println("Thread.currentThread().getContextClassLoader().getParent()" + Thread.currentThread().getContextClassLoader().getParent());
            System.out.println("ClassLoader.getSystemClassLoader():" + ClassLoader.getSystemClassLoader());
//             if you call Class.forName() without specifying the classloader, it will use the system classloader by default;
//            Class<?> claz1 = Class.forName("com.hundsun.SimpleApp");
//            Class<?> claz1 = Class.forName("com.hundsun.SimpleApp",true,urlContextClassLoader);
            // if you call Class.forName() with the same class and same classloader multiple times,
            // the class will be loaded and initialized only once
//            Class.forName("com.hundsun.SimpleApp",true,urlContextClassLoader);
//            Class.forName("com.hundsun.SimpleApp",true,urlContextClassLoader);
            // ClassLoader.loadClass will only load the class but will not initialize it - the static code block will not be executed;
            Class claz1 = urlContextClassLoader.loadClass("com.hundsun.SimpleApp");
            System.out.println("claz1.getClassLoader()" + claz1.getClassLoader());
        } catch (Exception e) {
            System.out.println(e.getCause());
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 扫描lib下面的所有jar包
     *
     * @return
     */
    private static List<File> scanDir(String path) {
        List<File> list = new ArrayList<>();
        File[] files = new File(path).listFiles();
        for (File f : files) {
            if (f.isFile() && f.getName().endsWith(".jar")) {
                list.add(f);
            }
            if (f.listFiles() != null) {
                for (File file : f.listFiles()) {
                    if (file.isFile() && file.getName().endsWith(".jar")) {
                        list.add(file);
                    }
                }
            }
        }
        return list;
    }
}

8 知识总结

  • JVM 类加载器是JRE的一部分,负责在 JVM 程序运行时,动态加载 JAVA 类到 JVM 内存中;

  • JRE 中内置的类加载器有 sun.misc.Launcher.AppClassLoader/sun.misc.Launcher.ExtClassLoader/java.net.URLClassLoader/bootstrap class loader 等;

  • 类加载器加载类,遵循了双亲委派模型,其核心思想是:

    • 当类加载器收到一个类或资源的加载请求后,会首先搜索该类或资源是否已经被加载到内存中了,如果已经被加载了的话,不会重复加载该类或资源;(在内存中搜索类或资源时,一样会遵循类的可见性);

    • 如果没有被加载,则会将加载请求委派给父级加载器去加载;

    • 父级加载器一样遵循向上委派机制,将加载请求委派给其父级加载器去加载,通过这种向上传导关系,所有的类加载请求,最终都会被传入到启动类加载器(Bootstrap ClassLoader)中;

    • 只有当父级加载器反馈无法完成特定类或资源的加载请求时,即父级加载器在它的搜索范围内找不到该类或资源时(搜索范围是磁盘上对应的目录),子级加载器才尝试在自己的搜索范围内加载类或资源;

  • 通过双亲委派模型,JVM 确保了:类的唯一性 uniqueness, 类的可见性 Visibility, 类的安全性 Security;

  • 双亲委派模型也有不足,比如 java.sql.DriverManager/java.sq.Driver 等是 jre 核心内部类,在 $JAVA_HOME/jre/lib/rt.jar 中,由 bootstrap classloader 负责加载他们;而 jdbc 实现类如 oracle.jdbc.driver.OracleDriver 是在程序运行时指定的类加载路径下的某个 jar 包中(类加载路径是通过环境变量 classpath 动态指定的),由 application class loader 负责加载他们;所以当 bootstrap classloader 加载的 java.sql.DriverManager, 需要调用 application classloader 加载的 java.sq.Driver 的具体实现类 oracle.jdbc.driver.OracleDriver 时,由于类的可见性 (子类加载器加载的类对父类加载器加载的类默认不可见),按照双亲委派模型,这是不 work 的;

  • 双亲委派模型的不足, 可以通过 thread Context Classloaders 来弥补,JDBC/JNDI 等的实现,都使用了thread Context Classloaders;比如方法 java.sql.DriverManager#getConnection(java.lang.String, java.util.Properties, java.lang.Class<?>) 就借用了 thread context classloader, 来加载 jdbc 实现类如 oracle.jdbc.driver.OracleDriver,具体源码可见:

  • callerCL = Thread.currentThread().getContextClassLoader(); // 获得thread context classloader
    aClass =  Class.forName(driver.getClass().getName(), true, classLoader); // // 使用获得的 thread context classloader,通过 Class.forName 加载 jdbc 实现类如 oracle.jdbc.driver.OracleDriver

  • 当 JVM 内置的加载器满足不了需求时,可以自定义类加载器, 自定义类加载器只需要继承类 ClassLoader 并复写方法 findClass() 即可;

  • 除了由 JRE 隐式动态加载类,还可以在代码中显示加载类;

  • 显示加载类,可以通过 Class.forName(String name, boolean initialize,ClassLoader loader) 或 Class.forName(String name) 加载,其中后者默认会使用 system classloader,且默认会初始化类 (即执行类的静态代码块);

  • 显示加载类,也可以通过 classLoader.loadClass(String name) 来加载类,此时只会加载类但不会初始化类(即不会执行类的静态代码块);

  • 通过 classLoader.loadClass(String name) 来显示加载类时,类加载器可以是当前类加载器/系统类加载器/线程上下文类加载器/或新创建的URLClassLoader:

    • 通过当前类加载器加载:Class.forName("com.hundsun.SimpleApp",true,urlContextClassLoader);

    • 通过系统类加载器加载:ClassLoader.getSystemClassLoader().loadClass("com.hundsun.SimpleApp");

    • 通过线程上下文类加载器加载:Thread.currentThread().getContextClassLoader().loadClass("com.hundsun.SimpleApp");

    • 新建URLClassLoader并进行加载: ClassLoader prevContextClassLoader = Thread.currentThread().getContextClassLoader(); URLClassLoader urlContextClassLoader = new URLClassLoader(urls, prevContextClassLoader);urlContextClassLoader.loadClass("com.hundsun.SimpleApp");

    • 新建URLClassLoader时可以指定其父类,当不指定时其父类默认是系统类加载器加载;

!关注不迷路~ 各种福利、资源定期分享!欢迎小伙伴们扫码添加明哥微信,后台加群交流学习。

IT明哥
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明哥的IT随笔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值