要实现具备优先级排序和条件过滤的SPI载入,核心是在ServiceLoader基础上封装:通过@SpiMeta注解声明priority和enabledWhen元数据,手动解析META-INF/services文件、反射读取注解、SpEL条件过滤,再按priority降序排序并安全实例化。

要实现具备优先级排序和条件过滤的 SPI 载入,核心不是替换 ServiceLoader,而是在其基础上做一层可控的封装:保留标准 SPI 的可插拔性,再通过元数据声明(如注解或配置文件)注入优先级与条件逻辑,最后在加载时统一排序、过滤、实例化。
定义带元数据的 SPI 接口与实现类
标准 ServiceLoader 只认 META-INF/services/xxx 文件,不支持优先级或条件。所以需用注解显式声明元信息:
- 定义一个注解,例如
@SpiMeta,包含priority(int,默认0)、enabledWhen(String,表示 SpEL 表达式或简单键值对,如"os.name == 'Linux'"或"env == 'prod'") - 所有 SPI 实现类加上该注解,例如:
@SpiMeta(priority = 10, enabledWhen = "env == 'dev'")
public class DevLogger implements Logger { ... } - 注意:注解需声明为
@Retention(RetentionPolicy.RUNTIME),否则运行时读不到
编写自定义加载器,接管发现与筛选流程
不直接用 ServiceLoader.load(),而是手动读取 META-INF/services/ 下的服务名文件,解析类名,再反射检查注解并收集元数据:
- 从
ClassLoader.getSystemResources("META-INF/services/com.example.Logger")获取所有资源路径 - 逐行读取每个文件内容(即实现类全限定名),用
Class.forName()加载类 - 用
clazz.getAnnotation(SpiMeta.class)提取 priority 和 enabledWhen;若无注解,按默认值(priority=0, enabledWhen="true")处理 - 对每个候选类执行条件判断:可内嵌轻量 SpEL 解析器(如 Spring 的
StandardEvaluationContext),或更简单地用System.getProperty("env")匹配字符串
排序、去重与安全实例化
收集完所有符合条件的类后,按 priority 降序排列(高优先级先生效),再实例化:
- 用
Stream.of(...).filter(...).sorted(comparing(SpiHolder::getPriority).reversed())组织列表 - 避免重复加载同一类(尤其多模块含相同服务文件时),可用
Set<class>> seen</class>去重 - 实例化前校验构造函数是否 public 且无参;抛出明确异常(如
SpiLoadingException)而非让InstantiationException泛滥上抛 - 建议返回
List<t></t>而非单例——SPI 天然支持多个实现共存,调用方按需选或轮询
可选增强:支持外部配置覆盖优先级
硬编码 priority 不灵活。可在加载逻辑中引入配置优先级映射,例如读取 spi-priority.properties:
- 内容形如:
com.example.ProdLogger=20
com.example.MockLogger=5 - 若配置存在,则以配置值为准;否则 fallback 到注解值
- 这样运维可在不发版情况下调整生产环境行为(比如临时启用某个 Mock 实现)

















