双亲委派机制,懂吧~ 那什么情况下需要破坏它,知道吗?

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 双亲委派机制,懂吧~ 那什么情况下需要破坏它,知道吗?

一、什么是双亲委派机制?

我们要获得一个类的Class实例,可以采用如下方式:

那么在Class的forName(className)方法中,会根据是谁调用了Class.forName(className)这个方法,那么就获得当时加载了它的那个ClassLoader,然后,再通过类加载器来负责对类进行加载操作。

获得了类加载器之后,就可以通过如下的loadClass(...)方法对className执行类加载操作的。那么在如下的代码逻辑中,可以看到双亲委派机制的逻辑了。也就是说,如果自己没有加载过className这个类,则必须“委托”给父加载器执行加载操作。除非父类加载器无法加载这个类,才由自己执行加载操作

那么,为什么父类加载器无法加载某个类呢?原因就是,每个类型的加载器z都有其约束的加载路径,如果这个className没有在这个路径下,那么对应的类加载器就无法加载这个类了。如下所示:

二、什么情况下,需要破坏双亲委派机制?

我们以使用JDBC操作数据库时的代码为例:

public class JdbcTest {
    public static void main(String[] args) {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true", "root", "root");
        ... ...
    }
}

【解释】由于是JdbcTest类调用了Class.forName方法,并且JdbcTest类是由AppClassLoader加载的,所以先由AppClassLoader去查找自己是否加载过类"com.mysql.jdbc.Driver",发现没有加载过,那么委托ExtClassLoader去加载,而它之前也没有加载过这个类,那么委托给BootstrapClassLoader去加载,但是由于com.mysql.jdbc.Driver这个类在CLASSPATH中,而不在rt.jarext/*.jar里,所以最终还是会由AppClassLoader去加载。

当"com.mysql.jdbc.Driver"这个类加载后,就会执行它内部的静态方法,将new Driver()实例对象注册到DriverManager中,这样就完成了驱动注册操作。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {}
    // 静态块执行语句
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

在JDBC4.0以后,开始支持使用SPI的方式来注册这个Driver,具体做法就是在mysql的jar包中的META-INF/services/java.sql.Driver文件中指明当前使用的Driver是哪个。

那么,我们以往的数据库连接代码就可以改成这样:

public class JdbcTest {
    public static void main(String[] args) {
        // Class.forName("com.mysql.jdbc.Driver"); // 此处就不需要显示通过Class.forName(...)执行类的加载操作了
        Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true", "root", "root");
        ... ...
    }
}

这个不对啊?Driver在哪加载了呢?我们来看一下DriverManager类(JDK8)的静态方法就知道了。如下所示:

static {
    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;}
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); // 使用SPI加载Driver
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {}
            return null;
        }
    });
    if (drivers == null || drivers.equals("")) return;
    String[] driversList = drivers.split(":");
    for (String aDriver : driversList) {
        try {
            Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

【解释】在上面的代码中,我们看到了ServiceLoader.load(Driver.class)这行代码,它是基于SPI的方式去加载了Driver实例了。

那为啥要这么做呢?直接在DriverManager中编写Class.forName("com.mysql.cj.jdbc.Driver")这行代码不就可以了嘛,采用SPI难道是为了炫技嘛?

其实不是的,因为DriverManager这个类的全路径名是java.sql.DriverManager,它是在rt.jar包中的,所以它是由BootstrapClassLoader负责加载的。而com.mysql.cj.jdbc.Driver类不在rt.jar包中,所以BootstrapClassLoader无法加载,而BootstrapClassLoader又是顶层的类加载器了,它没有父加载器,所以既无法委托给父加载器负责加载,而自己又无法加载,那么就只能报错ClassNotFoundException了。

那这个问题怎么解决呢?SPI的出现解决了这个问题,下面我们来看一下SPI是如何处理的,即:

ServiceLoader.load(Driver.class);

那么问题又来了,这个Thread.currentThread().getContextClassLoader()是从当前线程的上下文中获得了ClassLoader,那到底这个ClassLoader是什么呢?想要找到答案,我们就需要看Launcher类了,它是负责用来创建类加载器的。

【解释】从上面代码我们可以看到,当前线程的上下文中获得的ClassLoader就是AppClassLoader,那么它加载的类路径就是CLASSPATH,就可以成功的加载"com.mysql.cj.jdbc.Driver"这个类了。它采用的这种方式,就是破坏双亲委托机制来加载Driver类了。

今天的文章内容就这些了:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享

更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
7月前
|
存储 Java 编译器
类加载机制和双亲委派机制
类加载机制和双亲委派机制
|
1月前
|
Java
类加载器和双亲委派机制
从父类加载器到子类加载器分别为: BootStrapClassLoader 加载路径为:JAVA_HOME/jre/lib ExtensionClassLoader 加载路径为:JAVA_HOME/jre/lib/ext ApplicationClassLoader 加载路径为:classpath 还有一个自定义类加载器
|
4月前
|
Java 编译器
什么是双亲委派机制?
什么是双亲委派机制?
261 59
|
4月前
|
前端开发 Java C++
双亲委派机制
这篇文章详细解释了Java中的双亲委派机制,包括其原理、类加载器的分类(启动类加载器、扩展类加载器、应用程序类加载器)以及它们之间的关系和作用。
|
5月前
|
关系型数据库 MySQL Java
spi机制打破双亲委派机制
在JDBC4及以上版本,连接MySQL数据库不再需要显式加载驱动(`Class.forName`),而是利用SPI机制。系统通过扫描`META-INF/services/java.sql.Driver`文件找到`com.mysql.cj.jdbc.Driver`并使用`ServiceLoader`由AppClassLoader加载。`DriverManager`在启动时加载所有可用的`Driver`实现,实现解耦和动态发现。虽然看起来逆向了双亲委派,但实际上每个类仍由适当的类加载器加载,保持了加载层次。
spi机制打破双亲委派机制
|
7月前
|
Java
【JVM】双亲委派机制、打破双亲委派机制
【JVM】双亲委派机制、打破双亲委派机制
64 1
|
Java 应用服务中间件 API
打破双亲委派机制
打破双亲委派机制
59 0
|
前端开发 Java
05-说下类加载器和双亲委派机制
在明白了整个类从加载到初始化的过程,接下来我们有必要来说下类加载器的概念,因为实现上述过程是必须依靠加载器来实现的。
72 0
05-说下类加载器和双亲委派机制
|
Java 应用服务中间件
JVM - 彻底理解打破双亲委派机制
JVM - 彻底理解打破双亲委派机制
123 0
JVM - 彻底理解打破双亲委派机制
|
缓存 Java API
双亲委派机制是什么?
如果跟同事谈“双亲委派”,难免显得很八股了,但是这个“双亲委派”却是JVM在类加载环节必不可少的一个操作,充分的理解它,能够使我们更加良好的理解JVM在加载类的时候背后细节。不仅如此,在学习了解一些其他的技术,例如:SPI、OSGI等等,也能相辅相成,融会贯通,可谓“两仪生四象,四象生八卦”
219 1
双亲委派机制是什么?