引言
SPI(Service Provider Interface)是 Java 中用于实现接口与实现解耦的核心机制。通过约定优于配置的方式,框架可以自动发现和加载服务实现类,从而支持插件化架构和动态扩展。
SPI 的本质:控制权的反转
API 与 SPI 的核心区别在于控制权的归属:
- API:服务提供方定义接口并提供实现,调用方被动接受。
- SPI:服务调用方定义接口,不同的服务提供方根据接口提供实现,调用方可动态选择使用哪个实现。
这种设计在日志框架 SLF4J 等场景中尤为常见,用户可根据需要切换 Log4j、Logback 等不同实现,而无需修改业务代码。
ServiceLoader 原理与优劣势
工作原理
ServiceLoader 是 JDK 实现 SPI 的核心工具,其工作机制如下:
- 约定目录:读取
/META-INF/services/下以接口全限定名命名的文件。 - 懒加载:仅在调用
iterator()或遍历时加载实现类。 - 缓存机制:已加载的实现类缓存在
LinkedHashMap中。 - 顺序迭代:按配置文件顺序依次加载。
优势
- 解耦:接口与实现分离,无需硬编码。
- 扩展性:新增实现只需添加配置,无需修改已有代码。
- 动态加载:支持插件化架构,增减模块无需重新发布系统。
局限性
- 线程不安全:不能保证单例,多线程环境需谨慎。
- 性能开销:可能全量加载未使用的实现类。
- 健壮性差:配置错误(如类未找到)会抛出异常而非优雅降级。
- 缺少命名机制:无法在程序中准确引用特定实现,只能遍历所有。
Dubbo 对 SPI 的优化
Dubbo 的 ExtensionLoader 在 JDK SPI 基础上进行了多项优化:
按需加载
通过 key-value 方式给每个实现类命名,支持按需获取:
ExtensionLoader<DemoSpi> extensionLoader = ExtensionLoader.getExtensionLoader(DemoSpi.class);
DemoSpi demoSpi = extensionLoader.getExtension("demoSpiImpl");
IOC 和 AOP 支持
在创建扩展实例时,Dubbo 支持依赖注入(IOC)和包装类(AOP),可自动为扩展对象添加监听、日志等包装逻辑。
多目录加载
支持从多个目录加载配置,兼顾兼容性与扩展性:
META-INF/services/:兼容 JDK SPIMETA-INF/dubbo/:Dubbo 自定义扩展META-INF/dubbo/internal/:内部扩展


