当前位置: 首页 > news >正文

DriverManager在rt.jar里,凭什么能加载到classpath下的驱动?

图片

故事场景:皇帝需要一位“外来的专家”

我们续接上一个故事。王朝的官员体系(类加载器)等级分明:皇帝 > 总督 > 县令。祖宗家法(双亲委派)规定,办事必须层层上报。

新的任务来了:
王朝的“皇家运输署” (DriverManager),由皇帝(启动类加载器)直接管辖,负责管理全国的运输。最近,从西域(第三方厂商)引进了一种全新的交通工具——“MySQL牌飞毯” (mysql-connector-java.jar)。这种飞毯需要一位专门的“飞毯驾驶员” (com.mysql.cj.jdbc.Driver) 才能操作,而这位驾驶员是一位隐居在某个县城(classpath)里的民间高手。

祖宗家法的困境

“皇家运输署”的主管(DriverManager的代码)接到了命令:“去,把那位‘飞毯驾驶员’给我找来并登记在册!”

主管顿时犯了难。他是皇帝身边的人,按照“向上通报”的祖宗家法,他只能向皇帝汇报,他压根没有渠道、也没有权力去一个偏远县城里直接找人。而县令也没有接到任何请求,自然不会上报他县里有这么一位高手。这成了一个死局。

皇帝的智慧:颁布“招贤令”并派出“钦差”

皇帝(Java的设计者)早已预料到这种情况。他想出了一个绝妙的办法来“打破”常规:

  1. 1. 颁布“招贤令” (SPI 机制):
    皇帝下了一道圣旨,传遍天下:“所有身怀绝技的‘交通工具驾驶员’,不必等朝廷征召,可主动到本地县衙的‘专家名录’META-INF/services/java.sql.Driver文件)上登记自己的名号和住址!”
    于是,那位“飞毯驾驶员”就在他所在县城的名录上写下了自己的大名。

  2. 2. 派出“钦差” (线程上下文类加载器):
    现在,“皇家运输署”的主管要找人了。他没有亲自出马,而是采取了以下步骤:

    • • 他查看了当前正在执行的“引进飞毯”这项国家工程(当前线程)。

    • • 他发现,这个工程的发起地是那个偏远县城,因此工程团队里有一位来自当地的联络官——县令本人Thread.currentThread().getContextClassLoader())。

    • • 主管(DriverManager)于是把皇帝的“招贤令”交给这位联络官,并命令道:

      “本官乃朝廷命官(由父加载器加载),不便直接去你的地盘上找人。但你,作为本工程的联-络官(线程上下文类加载器),有这个权限。现命你,拿着这份招贤令,去你的县里,按照专家名录上的记载,把那位‘飞毯驾驶员’给我请过来!”

  • • 结果:
    县令(应用程序类加载器)愉快地接受了来自“上级”的“逆向委托”。他回到自己的地盘,轻松找到了那位“飞毯驾驶员”,并成功地将他引荐给了皇家运输署。

故事总结:

概念

双亲委派的困境与解决方案
核心矛盾上级(父)看不见下级(子)

。皇家运输署(由皇帝加载)无法找到民间高手(由县令加载)。

解决方案SPI + TCCL

 (招贤令 + 钦差)

SPI (招贤令)

提供一个“约定”,让下级可以主动暴露自己能提供哪些服务。

TCCL (钦差)

提供一个“通道”,让上级可以临时借用下级的权力,去加载下级才能看到的类。

是否“打破”

它没有修改“向上委托”的家法本身,而是绕过了它。是一种从上到下的“逆向调用”,而非“从下到上”的加载。

一句话总结祖宗家法(双亲委派)不许爹找儿子,但爹可以命令跟着自己的“儿子代表”(线程上下文类加载器)回家办事。

结论:
JDBC之所以需要“打破”双亲委派,是因为Java的核心API(由父加载器加载)需要动态加载由应用程序提供的、具体的实现类(由子加载器加载)。这种“跨层级”的调用需求,通过线程上下文类加载器这个精巧的设计,得以完美解决。这不仅限于JDBC,在JNDI、JCE等许多需要SPI的场景中,都使用了同样的技术。

技术解析

核心矛盾:谁来加载驱动?

让我们先回顾一下“双亲委派模型”和JDBC的“身份”:

  1. 1. 双亲委派模型: 一个类加载器接到加载任务后,会先向上委托给父加载器,层层上报,直到顶层的启动类加载器 (Bootstrap ClassLoader)。只有当所有父加载器都找不到时,子加载器才会自己尝试加载。

  2. 2. JDBC的APIDriverManagerConnectionStatement 等核心接口和类,是Java语言的标准组成部分。它们位于 java.sql 包下,由最顶层的启动类加载器 (Bootstrap ClassLoader) 加载。

  3. 3. JDBC的驱动: 比如 mysql-connector-java.jar 里的 com.mysql.cj.jdbc.Driver 类,它是一个第三方厂商实现的。它被放置在你的应用的classpath下,因此它是由应用程序类加载器 (Application ClassLoader) 来加载的。

矛盾出现了:
DriverManager (由启动类加载器加载) 需要去加载并管理各种不同的 Driver 实现 (由应用程序类加载器加载)。

按照双亲委派模型,一个父加载器(启动类加载器)是无法看到无法加载其子加载器(应用程序类加载器)路径下的类的。这就好比皇帝(父)无法直接调用一个县城里的民间艺人(子),因为正常的流程是县令(子)请求皇帝(父)。这形成了一个无法解决的死循环。

解决方案:SPI + 线程上下文类加载器 (TCCL)

为了解决这个“逆向”加载的难题,Java引入了 SPI (Service Provider Interface) 机制,并通过线程上下文类加载器 (Thread Context Class Loader) 来打破双亲委派的限制。

  • • SPI 约定:
    JDBC 4.0 以后,驱动jar包会遵循SPI规范,在 META-INF/services/ 目录下放置一个名为 java.sql.Driver 的文件。
    这个文件的内容就是驱动实现类的全限定名,比如 com.mysql.cj.jdbc.Driver

  • • 打破双亲委派的关键动作:
    DriverManager 在初始化时,它不会使用自己的加载器(启动类加载器) 去加载这些驱动。相反,它会这样做:
    // DriverManager 内部的简化逻辑
    public class DriverManager {static {// ...loadInitialDrivers();// ...}private static void loadInitialDrivers() {// 1. 获取当前线程的“上下文类加载器”//    这个加载器通常是 AppClassLoader,它能看到 classpath 下的驱动 jar 包ClassLoader cl = Thread.currentThread().getContextClassLoader();// 2. 使用 ServiceLoader 工具类,并传入这个“借来的”加载器//    ServiceLoader 会根据 SPI 约定去 META-INF/services/ 目录下查找驱动ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class, cl);// 3. 遍历找到的驱动实现,并尝试加载和注册它们for (Driver driver : loadedDrivers) {// ... 注册驱动 ...}}
    }
    Thread.currentThread().getContextClassLoader() 这行代码就是“破局”的关键。它允许一个由父加载器加载的类(DriverManager),“借用”子加载器的“视野”去加载子加载器才能看到的类。
http://www.dtcms.com/a/300182.html

相关文章:

  • CPA战略-4.1-公司战略与组织结构
  • 人形机器人_双足行走动力学:弹性势能存储和步态能量回收
  • 聚类里面的一些相关概念介绍阐述
  • 杰理蓝牙耳机开发--三轴加速度传感器与IIC通信
  • Python:PyAutoGUI模拟鼠标移动点击事件,程序运行后,如何获取鼠标控制权了?
  • Redis的数据淘汰策略是什么?有哪些?
  • 昇思学习营-【模型开发与适配】学习心得_20250724
  • window上建立git远程仓库
  • Sklearn 机器学习 数值指标 entropy熵函数
  • Linux网络-------1.socket编程基础---(TCP-socket)
  • base64魔改算法 | jsvmp日志分析并还原
  • 在 Dell PowerEdge T440 上通过 iDRAC9 安装 Proxmox VE
  • Flutter开发实战之网络请求与数据处理
  • bmp280的压力数据采集(i2c设备驱动+设备树编写)
  • ACO-OFDM 的**频带利用率**(单位:bit/s/Hz)计算公式
  • 建筑施工场景下漏检率↓76%!陌讯多模态融合算法在工程安全监控的落地实践
  • OpHReda精准预测酶最佳PH
  • 进制间的映射关系
  • 2025牛客暑期多校第4场——G
  • Polyhedral Approaches in Combinatorial Optimization组合优化中的多面体方法(下)
  • Java实现大根堆与小根堆详解
  • 每日面试题15:如何解决堆溢出?
  • 如何检查服务器数据盘是否挂载成功?
  • Android-三种持久化方式详解
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-32,(知识点:模数转换器,信噪比,计算公式,)
  • 深入理解C语言快速排序与自省排序(Introsort)
  • 【每天一个知识点】GAN(生成对抗网络,Generative Adversarial Network)
  • Compose笔记(三十八)--CompositionLocal
  • 安卓学习记录1——持续更新ing
  • React组件中的this指向问题