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

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.xdnf.cn/news/19987.html

相关文章:

  • sealos部署k8s
  • 华为校招实习留学生机试全攻略:真题目录+算法分类+在线OJ+备考策略
  • 如何将两个网段互相打通
  • Java场景题面试合集
  • 「数据获取」中国科技统计年鉴(1991-2024)Excel
  • 江协科技STM32学习笔记补充之004
  • ETL VS ELT企业应该怎么选择数据集成方式
  • 前缀和和差分思路理解以及典题题解
  • Java面试宝典:Redis的设计、实现
  • Flash Attention vs Paged Attention:大语言模型注意力计算的内存管理革命
  • 【国内电子数据取证厂商龙信科技】IOS 逆向脱壳
  • Milvus快速入门以及用 Java 操作 Milvus
  • PAT 1093 Count PAT‘s
  • [技术革命]Harmonizer:仅20MB模型如何实现8K图像_视频的完美和谐化?
  • 三高项目-缓存设计
  • k8s证书理论知识之/etc/kubernetes/pki/ 和/var/lib/kubelet/pki/的区别
  • 将 PDF 转换为 TIFF 图片:简单有效的 Java 教程
  • 23种设计模式——抽象工厂模式(Abstract Factory Pattern)详解
  • 实战复盘:pnpm Monorepo 中的 Nuxt 依赖地狱——Unhead 升级引发的连锁血案
  • Node.js 18+安装及Claude国内镜像使用、idea中claude插件下载指南
  • MMD动画(二)动作制作
  • Spring线程池ThreadPoolTaskExecutor‌详解
  • 大语言模型的“思考”逻辑:从Token生成到上下文理解的内部流程
  • 我的创作纪念日——《惊变365天》
  • 裸签、Attach、Detach及其验签方式
  • Docker学习笔记(二):镜像与容器管理
  • 基于STM32的智能家居环境监控系统设计
  • 如何看懂GPU架构?万云智算一分钟带你了解GPU参数指标
  • Matter安全实现
  • Deathnote: 1靶场渗透