当前位置: 首页 > 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.lryc.cn/news/600783.html

相关文章:

  • Vue当中背景图无法占满屏幕的解决方法
  • 记一次腾讯云临时密钥接管存储桶
  • 零基础 “入坑” Java--- 十四、【练习】图书小系统
  • mrpc框架项目的AI总结
  • 热传导问题Matlab有限元编程 :工业级热仿真核心技术-搭建热传导求解器【含案例源码】
  • 【ELasticsearch】节点角色分类与作用解析
  • ubuntu下docker安装thingsboard物联网平台详细记录(附每张图)
  • 考研复习-数据结构-第八章-排序
  • 求hom_math_2d的角度值
  • URL与URI:互联网世界的“门牌号“与“身份证“
  • DocC的简单使用
  • ICMP报文工作原理
  • Linux如何执行系统调用及高效执行系统调用:深入浅出的解析
  • Python 数据分析(二):Matplotlib 绘图
  • 斐波那契数列加强版 快速矩阵幂
  • 特产|基于SSM+vue的南阳特产销售平台(源码+数据库+文档)
  • Linux 系统调用详解:操作文件的常用系统调用
  • SSE (Server-Sent Events) 服务出现连接卡在 pending 状态的原因
  • 2025微前端架构研究与实践方案
  • JavaScript里的string
  • 前端设计中如何在鼠标悬浮时同步修改块内样式
  • 【机器学习深度学习】LLamaFactory微调效果与vllm部署效果不一致如何解决
  • k8s的nodeport和ingress
  • 什么是JUC
  • Voxtral Mini:语音转文本工具,支持超长音频,多国语音
  • 9.3 快速傅里叶变换
  • Docker常用命令详解:以Nginx为例
  • gig-gitignore工具实战开发(五):gig add完善
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 热词评论查询功能实现
  • Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成