服务声明(uses/provides)不直接简化变量初始化,而是通过解耦“创建”与“使用”实现可插拔初始化:只需声明需求与提供者,ServiceLoader 自动发现加载,配合封装可一行获取实例,适用于无参、多选一策略对象。

模块系统中的服务声明(uses 和 provides)本身不直接简化变量初始化流程,但它能帮你把“谁来创建对象”和“谁来使用对象”解耦,从而让初始化更清晰、可维护、可替换——这才是真正的简化。
服务声明帮你绕开硬编码的 new 实例
传统方式中,你可能这样初始化一个策略对象:
PaymentProcessor processor = new AlipayProcessor(); // 或 new WechatProcessor()
一旦新增支付方式,就得改代码、重新编译。而用服务机制后,你可以写:
ServiceLoader
PaymentProcessor processor = loader.findFirst().orElseThrow();
只要模块里有 provides PaymentProcessor with AlipayProcessor;,运行时自动发现并加载——初始化逻辑不变,但具体实现完全可插拔。
module-info.java 中的 uses 是“需求声明”,不是初始化器
uses 只告诉 JVM:“本模块需要某类服务”,它不创建实例,也不指定哪个实现。但它触发了编译期检查和运行时查找机制:
- 编译时:如果没在 classpath/modulepath 上找到至少一个
provides实现,不会报错,但ServiceLoader查不到任何服务 - 运行时:JVM 根据
uses类型扫描所有已加载模块的provides声明,构建可用服务列表 - 关键点:你仍需手动调用
ServiceLoader.load(),但不再需要 if-else 或配置文件解析来选实现
配合依赖注入风格,让初始化真正“自动”
单纯靠 ServiceLoader 还要写样板代码。可以封装一层:
public class ServiceFactory {
public static
return ServiceLoader.load(service).findFirst()
.orElseThrow(() -> new IllegalStateException("No provider for " + service));
}
}
然后初始化就变成一行:
EmailSender sender = ServiceFactory.getFirst(EmailSender.class);
这个调用不依赖具体模块名、不读配置、不反射字符串类名——只依赖接口和模块声明,安全又简洁。
注意边界:服务不是万能初始化工具
服务机制适合“多选一”的策略型对象(如序列化器、校验器、通知渠道),但不适合以下情况:
- 需要传参构造的对象(如
new DatabaseConnection("jdbc:mysql://..."))——服务提供者必须有无参构造或静态工厂方法 - 单例生命周期管理(服务每次
findFirst()都可能新建实例)——需自行加缓存或用 Provider 包装 - 跨模块共享状态或复杂依赖链——这时更适合轻量级 DI 容器或手动组合
它简化的是“选择哪个实现”,而不是“怎么构造这个实现”。把这两层分开想,才能用对。

















