关于JAVA SPI的面试题还蛮多的正好来深度分析一下。在此之前先看一道面试题。面试官问“Java的SPI机制是什么”你答“META-INF/services里放配置文件ServiceLoader加载。”面试官追问“那JDBC 4.0为什么不用写Class.forName了Bootstrap ClassLoader看不见MySQL驱动的。”是不是就有点被问倒了? 哈哈下文尝试一下来回答这个问题。本篇从三个层面来讲SPI是什么、解决了什么痛点TCCL怎么让父加载器加载到子加载器的类面试核心考点Dubbo/ES/Kafka Connect在SPI基础上做了什么增强Spring Boot为什么没用它读完本文后面试官再问SPI你能从META-INF/services一路讲到TCCL和Dubbo的Adaptive。SPI解决了什么痛点做过业务系统的人都知道框架如果只依赖具体实现类换MySQL驱动、换日志实现往往得改源码重新编译。搞中间件和基础库时这条路子走不通因为接口得稳定实现得能换。SPIService 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.DriverManager在rt.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 ServiceLoaderS load(ClassS 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只准备好loaderclasspath还没扫。真正干活的是迭代器。DriverManager初始化时拿到ServiceLoaderDriver的迭代器逐个next()。每往前一步JDK才去classpath找META-INF/services/java.sql.Driver读类名再反射创建实例。这叫懒加载第一次next()才暴露配置错误或类找不到。ServiceLoaderDriver loader ServiceLoader.load(Driver.class); IteratorDriver 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-info的provides/uses高ServicesCatalog先查传统 SPIMETA-INF/services/文件与模块化双轨并存模块里的provider还可以提供静态provider()方法返回实例不一定要无参构造。classpath上的命名模块实现在旧式扫描路径里可能被跳过做JPMS迁移时要单独留意。JDK SPI是底座中间件在底座上怎么改先看两个插件场景。Kafka Connect和Elasticsearch怎么用SPIElasticsearch和Kafka Connect都做插件新能力靠外部的jar接入不会在核心代码里把实现类名写死。两家都沿用META-INF/services/约定差别在ClassLoader和实例化时机。Kafka Connect标准SPI plugin.pathConnect的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为什么自己搞一套SPIJDK SPI只能全量迭代不能按名字取某一个实现也没有优先级、依赖注入。RPC框架扩展点多、组合关系复杂Dubbo在JDK SPI思路上自建了ExtensionLoader。配置文件路径变了META-INF/dubbo/internal/接口全名内容是name实现类例如dubboorg.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 SPIDubbo在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几节用自己的话说一遍。Q1SPI是什么SPI是JDK的服务发现机制框架定义接口第三方在META-INF/services/登记实现类ServiceLoader运行时加载。SPI是提供方实现框架定义的接口控制权在框架这边。Q2TCCL和JDBC驱动加载怎么串起来DriverManager在rt.jarMySQL驱动在应用classpath。ServiceLoader.load()无参重载取TCCL默认AppClassLoader从应用classpath扫描驱动。DriverManager.getConnection在类加载器为空或Platform时也回退TCCL。本质是TCCL作为桥梁让父加载器侧代码能加载子加载器可见的实现。Q3Dubbo SPI和JDK SPI有什么不同JDK SPI只能全量迭代无按名、无优先级、无注入。Dubbo用META-INF/dubbo/internal/和name实现类格式通过getExtension(name)按名加载Adaptive按URL选实现Activate做排序和条件激活Wrapper做装饰。JDK SPI负责发现Dubbo SPI负责发现组装。Q4Spring Boot自动配置是JDK SPI吗不是。Spring Boot走META-INF/spring/*.imports由SpringFactoriesLoader加载配合Conditional做条件装配。和JDK SPI是两条线不要搞混。最后如何评价Java SPI接口稳定、实现方在独立jar里、框架愿意自己做多实现筛选这类场景SPI仍然合适。JDBC驱动、ES和Kafka插件都是典型用法。原生SPI有三条硬局限不能按名字直接取实现只能迭代后自己筛没有优先级和条件激活多个实现共存时框架得另写规则只负责**new**对象不做依赖注入实现类里要用别的Bean得自己想办法希望这篇文章能帮到你。参考内容Java SE ServiceLoader 文档JDBC 4.0 驱动自动加载说明Dubbo ExtensionLoader 扩展机制