ServiceLoader 并未破坏双亲委派模型,而是通过 Thread.currentThread().getContextClassLoader() 主动绕过它,利用 TCCL 加载业务 classpath 下的 SPI 实现类,实现解耦与动态扩展。

ServiceLoader 本身不“破坏”双亲委派,而是**绕过它**——它通过线程上下文类加载器(TCCL)主动加载服务实现,从而让业务模块能加载自己 classpath 下的实现类,哪怕这些类对启动类加载器或扩展类加载器不可见。这种机制天然适配插件化、多租户、渠道变量等场景,是解耦“对象变量”的关键支点。
变量对象必须定义为标准 SPI 接口
业务变量不是配置项或常量,而是封装了“如何计算、何时生效、按什么优先级介入”的行为对象。接口需满足:
- public 修饰,独立发布(如 variable-spi.jar),避免与实现混编
- 方法签名明确:例如 String key()(唯一标识)、Object resolve(Context c)(动态取值)、int order()(排序依据)
- 不依赖 Spring 注解,不声明 @Component 或 @Service
META-INF/services 是变量注册的唯一入口
每个提供变量行为的模块(如渠道模块、租户模块)必须在 src/main/resources/META-INF/services/ 下放置文件,文件名严格等于接口全限定名(如 com.example.VariableProvider),内容为一行一个实现类全名:
- ✅ 正确:com.channel.AlipaySourceProvider
- ❌ 错误:com/channel/AlipaySourceProvider(斜杠)、com.example.variableprovider(大小写不符)、带 BOM 或末尾空格
- 多个实现可共存,文件中每行一个,ServiceLoader 全部发现并返回 Iterator
加载时必须对齐线程上下文类加载器
ServiceLoader 默认用 Thread.currentThread().getContextClassLoader() 查找资源。Web 容器、模块化应用或自定义线程池中,若 TCCL 被重置(如 Tomcat 的 WebAppClassLoader 未设为当前上下文),就会找不到自己 jar 中的配置文件。
- 显式传入类加载器更可靠:ServiceLoader.load(VariableProvider.class, this.getClass().getClassLoader())
- 避免缓存 ServiceLoader 实例——它不预加载,只在 iterator().next() 时反射实例化;提前缓存却未遍历,等于“假装加载成功”
- 首次调用 next() 可能抛 NoClassDefFoundError 或 InstantiationException,需捕获并跳过失效实现
变量使用端只面向接口,不感知来源
主流程代码完全不 import 任何实现类,也不 new、不配置 Bean:
- 遍历所有可用变量提供者,按 key() 匹配所需变量名,再按 order() 排序选取最高优的一个
- 支持运行时动态增删:只要新 jar 放入 classpath、含合法配置文件,下次遍历即生效
- 某个实现加载失败(如缺少依赖),ServiceLoader 自动跳过,不影响其他变量正常工作










