写点什么

【jvm】通过 JDBC 为例谈谈双亲委派模型的破坏

  • 2022 年 9 月 24 日
    江西
  • 本文字数:2110 字

    阅读完需:约 7 分钟

【jvm】通过JDBC为例谈谈双亲委派模型的破坏

作者石臻臻,CSDN 博客之星 Top5Kafka Contributornacos Contributor华为云 MVP,腾讯云 TVP,滴滴 Kafka 技术专家 KnowStreaming


KnowStreaming 是滴滴开源的Kafka运维管控平台, 有兴趣一起参与参与开发的同学,但是怕自己能力不够的同学,可以联系我,当你导师带你参与开源!

我们都知道类加载的双亲委派模型

双亲委派模型并不是一个强制约束模型,而是 java 设计者推荐给开发者的类加载实现方式;但是也会有例外; 今天我们主要来讲一讲 类似于 SPI 这种设计导致的双亲委派模型被“破坏”的情况;

1JDBC

不破坏双亲委派模型的情况(不使用 JNDI 服务)

         // 1.加载数据访问驱动        Class.forName("com.mysql.cj.jdbc.Driver");        //2.连接到数据"库"上去        Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=GBK", "root", "");
复制代码

Class.forName("com.mysql.cj.jdbc.Driver"); 这句会主动去加载类com.mysql.cj.jdbc.Driver

public class Driver extends NonRegisteringDriver implements java.sql.Driver {    //    // Register ourselves with the DriverManager    //    static {        try {            java.sql.DriverManager.registerDriver(new Driver());        } catch (SQLException E) {            throw new RuntimeException("Can't register driver!");        }    public Driver() throws SQLException {        // Required for Class.forName().newInstance()    }}
复制代码

可以看到,Class.forName()其实触发了静态代码块,然后向 DriverManager 中注册了一个 mysql 的 Driver 实现。这个时候,我们通过 DriverManager 去获取 connection 的时候只要遍历当前所有 Driver 实现,然后选择一个建立连接就可以了

破坏双亲委派模型的情况

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

 Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=GBK", "root", "");
复制代码

可以看到这里直接获取连接,省去了上面的 Class.forName()注册过程。现在,我们分析下看使用了这种 spi 服务的模式原本的过程是怎样的:

在这里插入图片描述

  1. 从 META-INF/services/java.sql.Driver 文件中获取具体的实现类名“com.mysql.cj.jdbc.Driver”

  2. 加载这个类,用 class.forName("com.mysql.jdbc.Driver")来加载

Class.forName()加载用的是调用者的 Classloader, 这个调用者 DriverManager 是在 rt.jar 中的,ClassLoader 是启动类加载器,而 com.mysql.jdbc.Driver 肯定不在<JAVA_HOME>/lib 下,所以肯定是无法加载 mysql 中的这个类的。这就是双亲委派模型的局限性了,父级加载器无法加载子级类加载器路径中的类。

如何解决父加载器无法加载子级类加载器路径中的类

我们分析一下,想要正常的加载,启动类加载器肯定不能加载,那么只能用应用类加载器能够加载,那么如果有什么办法能够获取到应用类加载器就可以解决问题了;我们看看 jdk 是怎么做的;

线程上下文类加载器

public class DriverManager {    static {        loadInitialDrivers();        println("JDBC DriverManager initialized");    }    private static void loadInitialDrivers() {        //省略代码        //这里就是查找各个sql厂商在自己的jar包中通过spi注册的驱动        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);        Iterator<Driver> driversIterator = loadedDrivers.iterator();        try{             while(driversIterator.hasNext()) {                driversIterator.next();             }        } catch(Throwable t) {                // Do nothing        }
        //省略代码    }}
复制代码

看这里,加载的时候去获取了一个加载器

    public static <S> ServiceLoader<S> load(Class<S> service) {        ClassLoader cl = Thread.currentThread().getContextClassLoader();        return ServiceLoader.load(service, cl);    }
复制代码

获取线程上下文类加载器Thread.currentThread().getContextClassLoader(); 这个值如果没有特定设置,一般默认使用的是应用程序类加载器;

总结

为了实现 SPI 这种模式,实现可插拔 做出了不符合双亲委派原则行为,但是这种破坏并不具备贬义的感情色彩,只要有足够意义和理由,突破已有的原则就可以认为是一种创新;

对于线程上下文类加载器 的实现类似于ThreadLocal 将变量传递到整个线程的生命周期; 这里无非就是将 ThreadLocal 里面存放的是应用类加载器;

发布于: 2022 年 09 月 24 日阅读数: 22
用户头像

关注公众号: 石臻臻的杂货铺 获取最新文章 2019.09.06 加入

进高质量滴滴技术交流群,只交流技术不闲聊 加 szzdzhp001 进群 20w字《Kafka运维与实战宝典》PDF下载请关注公众号:石臻臻的杂货铺

评论

发布
暂无评论
【jvm】通过JDBC为例谈谈双亲委派模型的破坏_JVM_石臻臻的杂货铺_InfoQ写作社区