provides和uses的核心作用是解耦模块间的服务契约与实现,使调用方仅依赖接口、不感知具体实现模块,通过ServiceLoader实现编译期无依赖、运行期动态绑定,支撑插件化与多环境策略。

provides 和 uses 的核心作用不是解耦变量,而是解耦模块间的服务契约与实现。它让调用方只依赖接口(服务类型),不感知具体实现类来自哪个模块,从而在编译期和运行期都实现真正的松耦合。
uses:声明“我要用什么服务”
在消费者模块的 module-info.java 中写 uses 接口全限定名,表示该模块会通过 ServiceLoader.load(Interface.class) 加载实现。JVM 会自动扫描所有已加载模块中对该接口的提供者声明。
- 接口必须定义在某个导出的包里(比如
com.example.service.Logger) - uses 不引入编译依赖,只启用运行时发现机制
- 同一个模块可 uses 多个不同接口,如
uses com.example.service.Auth, com.example.service.Cache;
provides…with…:声明“我实现了什么服务”
在提供者模块的 module-info.java 中写 provides 接口 with 实现类,表示该模块向系统注册了一个可用的服务实例。
- 实现类必须是 public、有无参构造器,且不能是匿名或本地类
- 一个接口可被多个模块 provides,ServiceLoader 默认按模块加载顺序返回全部实例
- 若需限定范围,可在
provides后加with多个类,例如:provides com.example.service.Logger with com.impl.FileLogger, com.impl.CloudLogger;
配合 ServiceLoader 完成运行时绑定
消费者模块不 new 实现类,也不 import 具体实现包,只通过标准 API 获取实例:
ServiceLoader<logger> loader = ServiceLoader.load(Logger.class);</logger>-
Optional<logger> logger = loader.findFirst(); // Java 16+</logger>或遍历获取 - 只要模块路径包含提供者模块,无需修改消费者代码即可切换日志实现
为什么这不算“对象变量解耦”,而更深层
它解耦的是模块职责与部署形态:消费者模块编译时不依赖任何实现模块,运行时才动态链接;实现模块可独立编译、替换、热插拔。这种解耦直接支撑插件系统、多环境策略(如测试用 MockLogger,生产用 Log4jLogger)、灰度发布等真实场景。

















