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

Java 双亲委派机制解析和破坏双亲委派的方式

最近在准备面试,正把平时积累的笔记、项目中遇到的问题与解决方案、对核心原理的理解,以及高频业务场景的应对策略系统梳理一遍,既能加深记忆,也能让知识体系更扎实,供大家参考,欢迎讨论。

在 Java 中,每个类都有一个对应的 类加载器(ClassLoader),负责将类的字节码加载到 JVM 中。Java 系统中,类加载器通常采用 双亲委派(Parent Delegation)机制,以保证 Java 核心类的安全和加载稳定性,但在插件化、多版本依赖或动态扩展场景下存在限制。开发者可以结合 自定义类加载器SPI 机制 灵活处理类加载问题。

---

一、类加载器分类

  1. 启动类加载器(BootstrapClassLoader)
    由 C++ 实现,加载 JAVA_HOME/lib 目录或通过 -Xbootclasspath 指定路径中的核心类库(如 rt.jar)。

  2. 扩展类加载器(ExtClassLoader)
    加载 JAVA_HOME/lib/ext 或通过 java.ext.dirs 指定路径下的类库。

  3. 应用程序类加载器(AppClassLoader)
    加载用户 classpath 下的类库。

  4. 自定义类加载器
    继承 java.lang.ClassLoader 实现,用于动态加载特定类或插件。用于扩展功能。


二、双亲委派机制概述

双亲委派机制核心思想:类加载请求先委派给父类加载器,父类加载器无法加载时,子类加载器才尝试加载。先父后子,父做得到,子不重复。

流程如下:

  1. 类加载器收到请求,先判断类是否已加载。
  2. 未加载时,向父类加载器委派 loadClass()
  3. 父类加载器无法加载,子类加载器再尝试加载。
  4. 父加载器为 null 时,由启动类加载器处理。

优点:

  • 核心类优先加载,保证安全性。
  • 避免类重复加载,防止类型冲突。
  • 提升程序稳定性,核心 API 不易被篡改。

三、破坏双亲委派的方式

虽然双亲委派机制可靠,但在某些场景下会被破坏:

  1. 自定义类加载器
    继承 ClassLoader 并重写 loadClass(),改变加载顺序。

  2. Tomcat 类加载器
    优先加载自己目录下的 class 文件,实现 Web 应用隔离。

  3. Java SPI(Service Provider Interface)
    SPI 通过 AppClassLoader 加载服务实现类,用于插件机制,绕过父加载器。


Java SPI 深入理解

Java SPI(Service Provider Interface)是一种服务发现机制,用于在运行时动态加载接口实现类。通过 SPI,Java 框架或程序可以在不修改代码的情况下,加载第三方实现类,实现插件化或扩展功能。

我们以 MySQL JDBC 驱动 为例,结合源码分析 SPI 的实现。


一、MySQL JDBC SPI 文件

MySQL JDBC 驱动在 JAR 包中有一个文件:

META-INF/services/java.sql.Driver

内容如下:

com.mysql.cj.jdbc.Driver
  • 文件名为接口全限定名 java.sql.Driver
  • 文件内容为实现类全限定名 com.mysql.cj.jdbc.Driver
  • 放在 JAR 包 META-INF/services/ 下,这是 SPI 的约定在这里插入图片描述

💡 注意:SPI 文件本身只是静态配置,只有被 ServiceLoader 加载时才生效。


二、Driver 接口源码分析

JDBC 定义了标准接口:
在这里插入图片描述

MySQL 提供了实现类 com.mysql.cj.jdbc.Driver,源码关键部分:
在这里插入图片描述

  • 静态块 static {} 会在类加载时执行,把 Driver 注册到 DriverManager
  • 核心逻辑:SPI 机制结合 ServiceLoaderDriverManager 自动加载,实现动态扩展。

三、SPI 加载机制源码分析

DriverManager 内部通过 SPI 加载驱动:
JDBC 驱动自动加载的核心逻辑——JDBC 通过 “ 先读系统配置、再用 SPI 机制 ” 的两步走策略,实现初始驱动的加载,无需开发者手动调用Class.forName(“驱动类名”)。
在这里插入图片描述

在这里插入图片描述

ServiceLoader.load() 核心源码逻辑:

  1. 为指定的服务接口或抽象类创建一个 ServiceLoader 实例,用于动态加载实现类。
  2. Thread.currentThread().getContextClassLoader();这是为了让服务加载与当前线程相关的类加载器一致

在这里插入图片描述

  • 查找配置文件方法META-INF/services/java.sql.Driver
    方法ServiceLoader.iterator() 简洁要点
    返回值:一个迭代器 Iterator<S>,用于遍历服务实现。
    延迟加载

    • 已缓存的实现(providers)优先返回。
    • 未缓存的实现通过 lookupIterator 才查找 META-INF/services/...、加载类并实例化。
      在这里插入图片描述
  • 实例化对象并注册:Class.forName(cn, false, loader) 会在运行时根据配置动态加载指定的类(全限定名如 com.mysql.cj.jdbc.Driver),无需在编译时写死类名,比较灵活。在这里插入图片描述* SPI 加载实现类:直接通过 AppClassLoader 或线程上下文类加载器加载第三方实现类,不走父加载器优先顺序。允许动态扩展,加载用户提供的驱动或插件,绕过父加载器限制。以 MySQL JDBC 为例,SPI 机制特点:

  1. 接口与实现解耦
    应用只依赖 java.sql.Driver,不依赖具体 MySQL 驱动。

  2. 动态加载实现类
    通过 ServiceLoaderDriverManager 自动发现 SPI 文件并实例化。

  3. 破坏双亲委派顺序
    直接由 AppClassLoader 加载用户驱动,实现插件化和动态扩展。

  4. 易于扩展
    添加新的数据库驱动,只需将 JAR 放入 classpath,不修改应用代码。

额外参考:日志框架logback的spi实现在这里插入图片描述

http://www.dtcms.com/a/366782.html

相关文章:

  • T检验(pearman)
  • 【全网最全】《2025国赛/高教杯》C题 思路+代码python和matlab+文献 一到四问 退火算法+遗传算法 NIPT的时点选择与胎儿的异常判定
  • 电商金融贷款服务市场趋势与竞争分析
  • [frontend]WebGL是啥?
  • 鸿蒙NEXT交互机制解析:从输入设备到手势响应的全面指南
  • Node.js 18+安装及Claude国内镜像使用、idea中claude插件下载指南
  • 【AI论文】UI-TARS-2技术报告:借助多轮强化学习推进图形用户界面(GUI)智能体发展
  • Django事务
  • 《Docker 零基础入门到实战:容器化部署如此简单,运维效率直接拉满》
  • 【有鹿机器人自述】我在社区的365天:扫地、卖萌、治愈人心
  • Android集成OpenCV4实例
  • Java 与 Docker 的最佳实践
  • docker更新jar包,懒人执行脚本
  • MaxKB4j智能体平台 Docker Compose 快速部署教程
  • 飞算JavaAI全面解析:重塑Java开发流程的智能引擎
  • 【数学建模】用Matlab玩转图论:从画图到求最短路径
  • 想要给文档加密?2025年顶尖文件加密软件分享
  • C++并发编程-23. 线程间切分任务的方法
  • uniapp vue页面传参到webview.nvue页面的html或者另一vue中
  • Web应用:返回图片URL
  • Python快速入门专业版(一):Windows/macOS/Linux 系统环境搭建(附常见报错解决)
  • 【连接器专题】案例:带屏蔽膜FPC出现概率性短路,真是供应商的锅?
  • EasyVoice与cpolar:构建私域有声平台的本地化方案
  • Spring线程池ThreadPoolTaskExecutor‌详解
  • 隔空盗刷、AI钓鱼、代理劫持…金融黑产竟进化至此?
  • Elasticsearch 8 中 Nested 数据类型的使用方法
  • 【iOS】 懒加载
  • 一文吃透 CSS 伪类:从「鼠标悬停」到「斑马纹表格」的 30 个实战场景
  • 中值滤波、方框滤波、高斯滤波、均值滤波、膨胀、腐蚀、开运算、闭运算
  • HTML图片标签及路径详解