Java架构直通车——以JDBC为例谈双亲委派模型的破坏

2023-11-06

引入

java给数据库操作提供了一个Driver接口:

public interface Driver {
    Connection connect(String url, java.util.Properties info)
        throws SQLException;
    boolean acceptsURL(String url) throws SQLException;
    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
                         throws SQLException;
    int getMajorVersion();
    int getMinorVersion();
    boolean jdbcCompliant();
    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}

然后提供了一个DriverManager来管理这些Driver的具体实现:

public class DriverManager {
    // List of registered JDBC drivers 这里用来保存所有Driver的具体实现
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {
        registerDriver(driver, null);
    }

    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {
        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }
        println("registerDriver: " + driver);
    }
}

这里省略了大部分代码,可以看到我们使用数据库驱动前必须先要在DriverManager中使用registerDriver()注册,然后我们才能正常使用。

JDBC4.0之前

在JDBC4.0之前,我们看下mysql的驱动是如何被加载的:

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

核心就是这句Class.forName()触发了mysql驱动的加载,我们看下mysql对Driver接口的实现:

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

可以看到,通过Class.forName(),其实触发了类加载,我们知道类加载机制分为:
加载->验证->准备->解析->初始化

在初始化阶段,通过反射也可以触发类的初始化,类初始化了,也就是出发了Driver类的静态代码块,通过这个静态代码库向DriverManager中注册了一个mysql的Driver实现。这个时候,我们通过DriverManager去获取connection的时候只要遍历当前所有Driver实现,然后选择一个建立连接就可以了。

JDBC4.0之后

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

然后使用的时候就直接这样就可以了:

 Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?characterEncoding=GBK", "root", "");

那么为什么JDBC4.0之后,就不需要显性去加载这个驱动了,难道rt.jar 里的DriverManager可以自主注册驱动包么,当然这是不可能的。

为了解决这个问题,jdk采用了上下文加载。

在你调用 DriverManager.getConnection方法时,会加载DriverManager类并执行他的静态方法:

	static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

我们进入该方法loadInitialDrivers()

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;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
               	try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                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);
            }
        }

其中ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class)就是上下文加载,其中的ServiceLoader 即为上下文加载工具包。

当代码执行到:
在这里插入图片描述
就会调用:
在这里插入图片描述
如你所见,其实他也是去使用Class.forName 去加载这个类,而且他还带了一个加载器的参数。

我们知道,如果单纯只用Class.forName()(不带参数)加载用的是调用者的Classloader,在JDBC4.0之前是不带参数的,也就是说,这里的调用者其实就是应用类加载器AppClassLoader,是可以向上委托进行加载的(虽然向上委托找不到Driver类,最终还是由应用类加载器自己加载)。

而如果是在DriverManager中单纯只用Class.forName()(不带参数)加载,这个调用者DriverManager是在rt.jar中的,ClassLoader是启动类加载器,也就是说由启动类加载器进行加载,而com.mysql.jdbc.Driver肯定不在<JAVA_HOME>/lib下,启动类加载器无法加载。这就是双亲委派模型的局限性了,父级加载器无法加载子级类加载器路径中的类。

那么应该怎么办呢?这个mysql的drvier 只有应用类加载器能加载,那么我们只要在启动类加载器中有方法获取应用程序类加载器,然后通过它去加载就可以了。这就是所谓的线程上下文加载器,也就是利用上下文加载的方式传递过来的AppClassloader进行加载。

这种自动加载采用的技术叫做SPI。很明显,线程上下文类加载器让父级类加载器能通过调用子级类加载器来加载类,这打破了双亲委派模型的原则。

SPI机制的约定:
在META-INF/services/目录中创建以接口全限定名命名的文件该文件内容为Api具体实现类的全限定名.
使用ServiceLoader类动态加载META-INF中的实现类,如SPI的实现类为Jar则需要放在主程序classPath中.
Api具体实现类必须有一个不带参数的构造方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java架构直通车——以JDBC为例谈双亲委派模型的破坏 的相关文章

  • IT公司智力题(持续跟新中)

    请听题 用赵本山在 买车 的语气 1 有1000瓶药物 但是其中有一瓶是有毒的 小白鼠吃了一个星期以后就会死掉 请问 在一个星期内找出有毒的药物 最少需要多少只小白鼠 解答 用二进制的思路去思考 1000瓶药代表了1000种状态 那么100
  • 编程技术面试的五大要点

    文 何海涛 扎实的基础知识 高质量的代码 清晰的思路 优化代码的能力 优秀的综合能力是编程技术面试的五大要点 找工作一直是一个热门话题 要想找到心仪的工作 难免需要经过多轮面试 编程面试是程序员面试过程中最为重要的一个环节 如果能在编程面试
  • 面试准备:操作系统常见面试题汇总

    文章目录 1 为什么要有用户态和内核态 内核态和用户态的运作方式 2 进程间通信方式介绍 3 Linux查看进程状态 cpu状态 占用端口的进程号的命令 linux top命令VIRT RES SHR DATA的含义 4 什么是Swap 5
  • Java架构直通车——过滤器、拦截器、AOP的区别

    文章目录 过滤器 拦截器 AOP 面向切面 三者使用场景 过滤器 过滤器拦截的是URL Spring中自定义过滤器 Filter 一般只有一个方法 返回值是void 当请求到达web容器时 会探测当前请求地址是否配置有过滤器 有则调用该过滤
  • Java架构直通车——结合源码理解PageHelper

    PageHelper实现方式 PageHelper首先将前端传递的参数保存到page这个对象中 接着将page的副本存放入ThreadLoacl中 这样可以保证分页的时候 参数互不影响 接着利用了mybatis提供的拦截器 取得Thread
  • 面试准备:Java新特性详解

    文章目录 Java语言新特性 1 Lambda表达式和函数式接口 2 接口的默认方法和静态方法 3 方法引用 4 重复注解 5 更好的类型推断 6 拓宽注解的应用场景 Java编译器新特性 参数名称 JVM的新特性 更多资料 参考 java
  • Java架构直通车——Kafka介绍和高性能原因

    文章目录 Kafka介绍 Kafka高性能原因 Kafka介绍 Kafka以前说过很多次了 包括了Kafka单独的介绍 Kafka与Fabric 这里知识简单说说 Kafka的主要特点就是基于Pull模式来处理消息消费 追求高吞吐量 一开始
  • 面试题目总结(CNN)

    CNN权值共享是什么 局部感知 即网络部分连通 每个神经元只与上一层的部分神经元相连 只感知局部 而不是整幅图像 滑窗实现 可行性 局部像素关系紧密 较远像素相关性弱 因此只需要局部感知 在更高层将局部的信息综合起来就得到了全局的信息 权值
  • Java架构直通车——Redis的PF实现原理:HyperLogLog

    文章目录 引入 什么是基数统计 基数统计的常用方法 HyperLogLog原理 再近一步 分桶平均 更近一步 真实的HyperLogLog 引入 之前的文章Java架构直通车 点赞功能用Mysql还是Redis 一文中 我们介绍了分别从my
  • Java架构直通车——分布式唯一 ID生成方案

    文章目录 分布式ID的几种生成方案 UUID MySQL主键自增 数据库自增ID改进方案 雪花算法 SnowFlake 雪花算法的优化 Redis自增id Zookeeper有序节点 最近要做区块链项目 要生成很多唯一ID做业务号之类的 所
  • Java架构直通车——DispatcherServlet详解

    文章目录 引入 DispatcherServlet处理流程 DispatcherServlet与WebApplicationContext 处理流程 DispatcherServlet源码分析 init service destroy 前文
  • Java架构直通车——基于Redis的Set NX实现分布式锁

    文章目录 实现原理 SetNx的缺陷 超时问题 单机 多机问题 实践 基于Redis的Set NX实现分布式锁 基于Redisson实现分布式锁 实现原理 我们先来看获取redis锁的set命令 SET resource name rand
  • Java架构直通车——Java中单体应用锁的局限性&分布式锁

    文章目录 前言 单体应用锁的局限性 什么是分布式锁 目前存在的分布式的方案 前言 通过之前的并发编程的学习 对JAVA中的锁有了深刻的理解 前面内容中讲到的锁都是有JDK官方提供的锁的解决方案 也就是说这些锁只能在一个JVM进程内有效 我们
  • 面试准备:海量数据的处理方式

    文章目录 背景 数据排序 分治 字典树 数据去重 哈希 压缩存储空间 面试题汇总 1 TopK 2 查找中位数 3 随机选择K个数 4 找出出现次数最多的IP 背景 海量数据的处理主要包括三个方面 数据排序 数据统计 数据计算 我们可以简单
  • Java中数字的应用

    Java中数字的应用 在java中经常会遇到比较大的数 甚至超过了long型 那么该如何处理这些 大数据 呢 在java中有两个类BigInteger和BigDecimal分别表示大整数类和大浮点数类 从原则上是可以表示 天文单位 一样大的
  • 【动态规划】最长公共子序列和最长公共子串(python)

    编写用时 2020年3月12日12 02 28 1h 动态规划经典例题 最长公共子序列和最长公共子串 python 很久之前大概是高中的时候写过这种题目 使用动态规划的方法求解的 现读研究生了 要把过去的拾起来的呢 1 最长公共子序列 LC
  • Java架构直通车——RabbitMQ集群架构模式

    文章目录 RabbitMQ四种架构模式 主备模式 远程模式 镜像模式 多活模式 RabbitMQ四种架构模式 主备模式 主备模式也被称为warren 兔子窝 一个主 备方案 主节点挂掉后 从节点提供服务 和ActiveMQ利用Zookeep
  • Java架构直通车——ThreadLocal实现RabbitMQ消息的批量发送

    文章目录 引入 什么是ThreadLocal 使用ThreadLocal 引入 之前 我们完成了单个消息的发送 以及单个消息发送的多线程池化 这里 我们继续完成批量发送消息的封装 因为rabbitMq本身是不支持批量发消息的 所以我们可以直
  • C++面试题目集合(持续跟新)

    与我前面写的C语言进阶知识点遥相呼应 这才是C 面试 网上的面试题有些太简单了 C 面试题目最多集中在对象的内存模型 记住了 如果用c c 内存都不清楚 还写个屁的程序 1 C 的虚函数是怎样实现的 C 的虚函数使用了一个虚函数表来存放了每
  • C++实现String类

    C 实现String类 还没有完成 待继续 有以下注意的点 1 赋值操作符返回的是一个MyString 而重载的 返回的是一个MyString 其中的原因参看 effective c 主要是返回引用的时候 必须返回必须在此函数之前存在的引用

随机推荐

  • 小程序实现弹幕功能-无限循环,不会重叠

    以下是支付宝小程序代码 先上效果图 以下是axml代码
  • QT小例子GUI(主)线程与子线程之间的通信

    QT小例子GUI 主 线程与子线程之间的通信 在主线程上 可以控制子线程启动 停止 清零 如果子线程启动的话 每一秒钟会向主线程发送一个数字 让主线程更新界面上的数字 ifndef TQT H define TQT H include
  • Caffe中 math_functions 分析

    本篇博客转载自 Caffe源码 一 math functions 分析 math function 定义了caffe 中用到的一些矩阵操作和数值计算的一些函数 这里以float类型为例做简单的分析 1 caffe cpu gemm temp
  • huggingface transformers 预训练模型加载参数设置

    说明 1 proxies 服务器无法直接访问互联网需通过代理访问 2 cache dir model及dadaset文件过大多次容易导致服务器存储过高 手工选择存储位置 model tokenizer from pretrained ber
  • TCP 滑动窗口详解(非常实用)

    一 滑动窗口简介 滑动窗口 Sliding window 是一种流量控制技术 早期的网络通信中 通信双方不会考虑网络的 拥挤情况直接发送数据 由于大家不知道网络拥塞状况 同时发送数据 导致中间节点阻塞掉包 谁也发不了数据 所以就有了滑动窗口
  • linux读取触摸屏事件数据

    对于有触摸设备的电脑或者手机 通过cat proc bus input devices应该就能够看到触摸设备的相关信息 比如 cat proc bus input devices I Bus 0013 Vendor 0x0012 Produ
  • 初学者该掌握的计算机知识,初学者该如何学习电脑知识

    看到不少刚入门的电脑刚入门者找不到适合自己的学习方法 到处碰壁 那么呢 接下来大家跟着小编一起来了解一下学习电脑知识的解决方法吧 初学者学习电脑知识方法 第一阶段 鼠标和键盘的操作 鼠标的操作主要是 移动 拖动 单击 双击和右击 知道鼠标的
  • 优化最小二乘支持向量机数据回归预测:基于鸽群算法与PIO-lssvm(附Matlab代码)

    优化最小二乘支持向量机数据回归预测 基于鸽群算法与PIO lssvm 附Matlab代码 支持向量机 SVM 是一种广泛应用于分类和回归问题的机器学习方法 然而 在SVR中 传统的最小二乘支持向量机 LSSVM 需要通过手动设置超参数来进行
  • Java webservice 客户端代码(四种方式)

    方式一 用hutool工具 String authenticationUrl http 139 196 206 126 8081 services WorkflowService wsdl HashMap
  • elasticsearch报错:DeprecationWarning: Passing transport options in the API method is deprecated.

    这个警告是因为您正在使用 Elasticsearch Python 客户端的一个过时的方式来传递传输选项 transport options 根据警告信息 现在建议使用 Elasticsearch options 方法来设置传输选项 以下是
  • 关于各种merge 的心得

    合并两个线性表 包括合并两个有序线性表 两个线性表相加等 第一 遍历两个表的时候 用 代替 空的那一方取0参与计算就可以了 这样就不用后面处理长的那个表剩下来的部分了 第二 对于进位 也放到 里去 这样不用后面处理最后是否有进位了
  • chain of thought 也就是 CoT思维链

    chain of thought 也就是 CoT 一经提出就引发了社区对它的热烈讨论 CoT 能够帮助大规模语言模型解决复杂的算术 常识及字符推理等任务 背景知识 语言模型 语言模型的本质是对任意一段文本序列的概率进行建模 如果将语言模型看
  • windows下redis设置redis开机自启动方法

    windows下redis设置redis开机自启动方法 ybb ymm的博客 CSDN博客 redis开机自启动设置windows
  • vue国际化处理

    什么是Vue国际化 国际化就是你的页面要面向的群众使用的语言不只是中文 还有其他语言 在面对其他人群时要切换页面的语言 说起来很高大上 实际上还是没有那么高级的 文件的组织 一般是在大型项目中使用的 我们需要将相关文件放置在哪里 建议是新开
  • LinearAlgebraMIT_11_MatrixSpace/Rank==1‘sMatrix/SmallWorldGraph

    x 1 矩阵空间 向量空间定义 满足加法和数乘的封闭性 就类似向量空间一样 也存在着矩阵空间的定义 举个例子 例如所有的3x3的矩阵构成的矩阵空间M 它的纬度就是9 如 1 0 0 1 对于M中所有对称矩阵组成子空间N1 维度为6 M中所有
  • java.sql.SQLException: No suitable driver found for jdbc:mysql:///XXX

    用Maven的父工程搭建DAO模块 并执行测试类 测试数据库查询时 报错 定位到DAO模块的StudentDao java源文件 发现是数据库连接出错 之前我也遇到类似问题 解决方案是在db properties文件中对jdbc url配置
  • pandas(series和读取外部数据)

    一 pandas概述 1 pandas介绍 pandas 是基于NumPy 的一种工具 该工具是为了解决数据分析任务而创建的 Pandas 纳入了大量库和一些标准的数据模型 提供了高效地操作大型数据集所需的工具 pandas提供了大量能使我
  • 期货逼仓攻略之郑商所版

    为什么80 的码农都做不了架构师 gt gt gt 新手入门 逼仓是期货交易所会员或客户利用资金优势 通过控制期货交易头寸或垄断可供交割的现货商品 故意抬高或压低期货市场价格 超量持仓 交割 迫使对方违约或以不利的价格平仓以牟取暴利的行为
  • 用简单的代码实现简易的rxjs的filter功能,帮助rxjs新手了解rxjs的操作符的工作原理。

    最近学习rxjs 在边学习的过程中边总结 希望可以帮助到大家 我模仿rxjs的功能自己在编写简介的类rxjs 这样可以加深我对它的使用 也可以让新手更快速入门 先从最简单的observable对象将起 我们正常使用rxjs如下 var Rx
  • Java架构直通车——以JDBC为例谈双亲委派模型的破坏

    文章目录 引入 JDBC4 0之前 JDBC4 0之后 引入 java给数据库操作提供了一个Driver接口 public interface Driver Connection connect String url java util P