news 2026/6/26 7:20:43

Java SPI,从双亲委派到Dubbo增强,面试官想听的在这

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java SPI,从双亲委派到Dubbo增强,面试官想听的在这

关于JAVA SPI的面试题还蛮多的,正好来深度分析一下。在此之前,先看一道面试题。

面试官问:“Java的SPI机制是什么?”

你答:“META-INF/services里放配置文件,ServiceLoader加载。”

面试官追问:“那JDBC 4.0为什么不用写Class.forName了?Bootstrap ClassLoader看不见MySQL驱动的。”

是不是就有点被问倒了? 哈哈,下文尝试一下来回答这个问题。

本篇从三个层面来讲:

  1. SPI是什么、解决了什么痛点
  2. TCCL怎么让父加载器加载到子加载器的类,面试核心考点
  3. Dubbo/ES/Kafka Connect在SPI基础上做了什么增强,Spring Boot为什么没用它

读完本文后,面试官再问SPI,你能从META-INF/services一路讲到TCCL和Dubbo的@Adaptive

SPI解决了什么痛点

做过业务系统的人都知道,框架如果只依赖具体实现类,换MySQL驱动、换日志实现,往往得改源码重新编译。搞中间件和基础库时,这条路子走不通,因为:接口得稳定,实现得能换。

SPI(Service Provider Interface)是JDK给出的约定:框架只依赖接口,第三方在jar里放**META-INF/services/配置文件,运行时由ServiceLoader**发现实现类。

JDBC是最常被引用的例子。JDBC 4.0之前,应用得自己写Class.forName("com.mysql.jdbc.Driver")把驱动类加载进来。4.0之后,DriverManager初始化时调用ServiceLoader.load(Driver.class),classpath上各厂商驱动jar在META-INF/services/java.sql.Driver里登记实现类,应用只写jdbc:mysql://...,不用知道驱动类全名。控制权从应用主动加载变成了框架被动发现。

JDK SPI需要的三样东西:

要素说明要求
接口框架定义的扩展契约公共接口
配置文件META-INF/services/<接口全名>UTF-8,每行一个实现类
实现类provider 具体类public 无参构造

和工厂+switch、配置文件里写类名再反射相比,SPI的差别在于约定统一

方式耦合度发现机制多实现共存
工厂 + switch框架要知道所有实现类名编译期写死加实现要改框架代码
配置类名 + 反射配置与代码分离,但无标准目录各自约定靠配置选一项
JDK SPI框架只依赖接口META-INF/services/<接口全名>天然支持多项,框架自行筛选

SPI的三要素齐了,但还有一个关键问题:配置文件在jar里,谁来扫?用什么ClassLoader扫?这就引出了TCCL。

TCCL(线程上下文类加载器):SPI为什么能加载到应用classpath上的类

先看一个矛盾:

java.sql.DriverManagerrt.jar里,由Bootstrap ClassLoader加载。你的com.mysql.cj.jdbc.Driver在应用classpath里,由AppClassLoader加载。

Bootstrap根本看不见AppClassLoader的东西,这是双亲委派模型的硬性规定。

那JDBC 4.0凭什么能做到不用写Class.forName

答案在TCCL(线程上下文类加载器)。ServiceLoader.load(Driver.class)无参重载时,取的是当前线程的上下文类加载器,默认指向AppClassLoader,从应用classpath扫描并加载驱动实现:

public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return new ServiceLoader<>(Reflection.getCallerClass(), service, cl); }

DriverManager.getConnection里也有类似逻辑:调用方类加载器为空或是Platform时,回退到TCCL。

准确说法:SPI通过TCCL主动绕过双亲委派的方向限制,让rt.jar里的框架代码能加载到应用classpath上的实现类。TCCL作为桥梁,让父加载器侧代码能加载子加载器可见的实现

面试时把TCCL和JDBC驱动加载串起来讲,一般就能答到点子上。

JDK SPI怎么工作

两方各干什么

SPI里固定有两个角色。**框架(消费方)**只依赖接口,比如java.sql.Driver。**实现方(provider)**在jar里放两样东西:实现类本身,以及META-INF/services/java.sql.Driver,里面写实现类全名,一行一个。MySQL、PostgreSQL各自登记,框架代码不用改。

调用时按什么顺序发生

很多人以为ServiceLoader.load(Driver.class)一执行就会实例化驱动,其实不是load只准备好loader,classpath还没扫。

真正干活的是迭代器DriverManager初始化时拿到ServiceLoader<Driver>的迭代器,逐个next()。每往前一步,JDK才去classpath找META-INF/services/java.sql.Driver,读类名,再反射创建实例。这叫懒加载:第一次next()才暴露配置错误或类找不到。

ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class); Iterator<Driver> it = loader.iterator(); Driver driver = it.next(); // 到这行才真正去扫配置文件、加载类

classpath上挂了多个驱动jar,配置文件会被合并,迭代器按顺序吐出多个Driver实例。DriverManager.getConnection(url)再逐个问能不能接这个URL,能接就用。SPI只负责找出来,选哪一个由框架定。带中间件接入的项目里,classpath同时挂MySQL和PostgreSQL驱动,最终用哪个取决于URL前缀匹配,不是SPI替你拍板。

DriverManager在初始化阶段对迭代中的异常做了catch,避免某个坏驱动拖垮全局——这是消费方的防御写法,不是SPI自带的容错。

实现方要满足什么

classpath部署时,实现类要有public无参构造,且能被当前ClassLoader加载。配置文件用UTF-8,#后面是注释,重复类名会跳过。

JDK 9之后走模块系统,SPI有两条并行路径:

机制配置方式优先级
模块化 SPImodule-infoprovides/uses高,ServicesCatalog先查
传统 SPIMETA-INF/services/文件与模块化双轨并存

模块里的provider还可以提供静态provider()方法返回实例,不一定要无参构造。classpath上的命名模块实现,在旧式扫描路径里可能被跳过,做JPMS迁移时要单独留意。

JDK SPI是底座,中间件在底座上怎么改?先看两个插件场景。

Kafka Connect和Elasticsearch怎么用SPI

Elasticsearch和Kafka Connect都做插件,新能力靠外部的jar接入,不会在核心代码里把实现类名写死。两家都沿用META-INF/services/约定,差别在ClassLoader和实例化时机

Kafka Connect:标准SPI + plugin.path

Connect的Source/Sink连接器、Converter、Transformation等,来自用户自带jar。Connect把plugin.path目录下的jar当插件库,启动时用ServiceLoaderScanner扫描。插件jar里要有对应接口的services文件,扫描器检查类能加载、public、有无参构造,才进可用列表。

Connect更贴近标准ServiceLoader规则。

Elasticsearch:自研SPIClassIterator,延迟实例化

ES启动时装插件,每个插件有自己的ClassLoader。PluginsService用自研的SPIClassIterator读services文件,只读类名、不马上**new**,避免classpath顺序和静态初始化把启动搞挂。和标准ServiceLoader比,ES多包这一层,因为插件ClassLoader隔离和启动顺序更复杂。

插件场景这么玩其实够用了,但是一些开源的RPC框架还要按名取、排序、注入,Dubbo因此自建了一套。

Dubbo为什么自己搞一套SPI

JDK SPI只能全量迭代,不能按名字取某一个实现,也没有优先级、依赖注入。RPC框架扩展点多、组合关系复杂,Dubbo在JDK SPI思路上自建了ExtensionLoader

配置文件路径变了:META-INF/dubbo/internal/<接口全名>,内容是name=实现类,例如dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

加载方式也变了:不是for (Protocol p : loader),而是按名取

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");

Dubbo在原生SPI之上补了四块能力:

特性作用
按名获取getExtension("dubbo"),不用全量迭代
@Adaptive按URL参数在运行时选具体实现
@Activate条件激活与order排序
Wrapper装饰器链包装扩展点

Dubbo没有替换JDK SPI规范,而是在RPC场景把发现、选择、组装做完整。

Spring Boot为啥不用JDK SPI

Dubbo在SPI之上做了增强,那Spring Boot呢?很多人以为Spring Boot的自动配置也是JDK SPI,其实不是

Spring Boot自动配置走的是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,由SpringFactoriesLoader演进而来,不是JDK SPI。别把@EnableAutoConfiguration说成JDK SPI。

对比项JDK SPISpring Boot imports
配置文件META-INF/services/接口全名META-INF/spring/*.imports
格式每行一个实现类每行一个配置类全名
典型用途JDBC、插件发现Starter自动配置

工业级框架的SPI演进对比

接口、配置文件、加载器,JDK SPI定了最小协议。Kafka Connect、Elasticsearch、Dubbo在同一思路上各自演进:改配置路径、换ClassLoader、控制实例化时机。

原理就讲到这里,下面我们过一遍高频的面试题。

面试里常问的四个问题

下面四个问题串起来,就是大多数SPI面试的主线。建议对照上文TCCL、Dubbo、Spring Boot几节,用自己的话说一遍。

Q1:SPI是什么

SPI是JDK的服务发现机制:框架定义接口,第三方在META-INF/services/登记实现类,ServiceLoader运行时加载。SPI是提供方实现框架定义的接口,控制权在框架这边

Q2:TCCL和JDBC驱动加载怎么串起来?

DriverManager在rt.jar,MySQL驱动在应用classpath。ServiceLoader.load()无参重载取TCCL(默认AppClassLoader),从应用classpath扫描驱动。DriverManager.getConnection在类加载器为空或Platform时也回退TCCL。本质是TCCL作为桥梁,让父加载器侧代码能加载子加载器可见的实现。

Q3:Dubbo SPI和JDK SPI有什么不同?

JDK SPI只能全量迭代,无按名、无优先级、无注入。Dubbo用META-INF/dubbo/internal/name=实现类格式,通过getExtension(name)按名加载;@Adaptive按URL选实现,@Activate做排序和条件激活,Wrapper做装饰。JDK SPI负责发现,Dubbo SPI负责发现+组装。

Q4:Spring Boot自动配置是JDK SPI吗?

不是。Spring Boot走META-INF/spring/*.imports,由SpringFactoriesLoader加载,配合@Conditional做条件装配。和JDK SPI是两条线,不要搞混。

最后,如何评价Java SPI

接口稳定、实现方在独立jar里、框架愿意自己做多实现筛选,这类场景SPI仍然合适。JDBC驱动、ES和Kafka插件都是典型用法。

原生SPI有三条硬局限:

  1. 不能按名字直接取实现,只能迭代后自己筛
  2. 没有优先级和条件激活,多个实现共存时框架得另写规则
  3. 只负责**new**对象,不做依赖注入,实现类里要用别的Bean得自己想办法

希望这篇文章能帮到你。


参考内容

  • Java SE ServiceLoader 文档
  • JDBC 4.0 驱动自动加载说明
  • Dubbo ExtensionLoader 扩展机制
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 7:20:33

【毕业设计】 基于移动端小程序的物联网菇房环境智能管理系统设计与实现 SpringBoot框架支撑的物联网菇房监测管控系统小程序(源码+文档+远程调试,全bao定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/26 7:17:13

从Del Pezzo曲面到有理六次曲线:Bertini对合与Coble曲面的构造

1. 项目概述&#xff1a;一条连接经典与深刻的几何路径如果你在代数几何里泡过一段时间&#xff0c;肯定会遇到一些名字听起来就很有分量的对象&#xff1a;Del Pezzo曲面、有理六次曲线、Coble曲面&#xff0c;还有Bertini对合。它们各自都是研究的热点&#xff0c;但你可能没…

作者头像 李华
网站建设 2026/6/26 7:14:01

2026年国企招投标数据服务平台选型指南:四大核心方案深度测评

2025年以来&#xff0c;国内国企招投标数字化转型步入深水区&#xff0c;随着《招标投标法》修订案对全流程电子化、全链路透明化的要求进一步明确&#xff0c;国企招投标工作从传统的“被动找标”转向“主动控商机、强合规、深分析”的精细化运营阶段。但当前不少处于选型评估…

作者头像 李华
网站建设 2026/6/26 7:12:10

GitHub Desktop中文汉化终极指南:5分钟实现全中文界面

GitHub Desktop中文汉化终极指南&#xff1a;5分钟实现全中文界面 【免费下载链接】GitHubDesktop2Chinese GithubDesktop语言本地化(汉化)工具 【GitHub桌面客户端中文汉化】 项目地址: https://gitcode.com/gh_mirrors/gi/GitHubDesktop2Chinese 还在为GitHub Desktop…

作者头像 李华