provides...with用于自身模块,声明该模块提供的服务接口与具体实现类的绑定关系,服务于ServiceLoader机制,不声明变量、不生成字段。
java 9 引入的模块系统(jpms)中,module-info.java 里的 provides...with 并不是用来“声明变量”,而是用于**服务加载机制(serviceloader)的模块化声明**——它告诉运行时:“本模块提供某个服务接口的具体实现”。真正被“声明”的是实现类与服务接口的绑定关系,而非变量本身。
理解 provides 的真实作用
provides 是模块对 java.util.ServiceLoader 的显式支持,解决的是“模块如何安全、可发现地暴露其实现类供其他模块使用”的问题。它不生成字段、不定义变量、不参与编译期类型推导,只在模块描述和运行时服务发现阶段起作用。
典型写法:
module my.impl {
requires my.api;
provides my.api.MessageService with my.impl.EmailService;
}
</font>
<H3>实战步骤:从接口到模块化服务加载</H3>
<p>按顺序完成以下四步,即可在真实项目中跑通:</p>
<ul>
<li><strong>定义服务接口(放在独立 API 模块)</strong>:<br>确保接口在 <code>module-info.java</code> 中用 <code>exports</code> 导出,且接口本身是 public 的。例如:<br><pre class="brush:php;toolbar:false;">package my.api;<br>public interface MessageService { void send(String msg); }
实现类必须是 public、非 abstract、有无参构造器。不能是匿名类或嵌套类(除非是 static)。例如:
package my.impl;<br>public class EmailService implements MessageService { ... }明确指定“我提供哪个接口,由哪个具体类实现”:
module my.impl {<br> requires my.api;<br> exports my.impl;<br> provides my.api.MessageService with my.impl.EmailService;<br>}注意:
exports my.impl 不是必须的——只要 ServiceLoader 能通过反射实例化,模块甚至可以不导出实现包;但若其他模块需直接 new 它,则需 exports。无需模块声明依赖实现模块(只需依赖 API 模块),用标准 ServiceLoader:
ServiceLoader<MessageService> loader = ServiceLoader.load(MessageService.class);<br>loader.forEach(service -> service.send("Hello"));JVM 会自动扫描所有已解析模块中对
MessageService 的 provides 声明,并加载对应实现。常见误区与关键细节
- provides 不等于 new 或注入:它只是注册映射关系。ServiceLoader 仍靠反射调用构造器,所以实现类必须可实例化;若构造器抛异常,加载时才失败,不是编译时报错。
-
多个模块可提供同一服务:ServiceLoader 默认按模块读取顺序返回全部实现;可用
findFirst()或遍历筛选。模块路径(--module-path)中靠前的模块优先被扫描,但不保证“覆盖”逻辑——这是应用层策略,非 JPMS 内置规则。 - 不能 provides 非 public 接口或内部类:服务接口必须被调用方模块可访问(即被其依赖的 API 模块 exports),且必须是顶层 public 类/接口。
-
IDE 和构建工具需识别模块结构:Maven 需启用
maven-compiler-plugin的release=9或更高,并配置<source>9</source>;IntelliJ 需开启 “Use module system” 并正确设置模块依赖。
为什么不用 provides 声明变量?替代方案是什么?
JPMS 不提供“模块级变量声明”机制。provides 绑定的是类型(接口 ↔ 实现类),不是值。若你需要跨模块共享配置值、常量或状态对象,应采用:
- 通过服务接口方法返回值(如
String getConfigKey()); - 用
System.getProperty()或ServiceConfigurationError外部传入; - 在 API 模块中定义 public static final 常量(需 exports 对应包);
- 借助 DI 框架(如 Dagger、Spring)在模块边界做依赖注入——此时
provides可作为框架扫描的线索之一,但非必需。
本质上,模块系统聚焦于“谁提供什么能力”,而非“谁持有哪个值”。把变量逻辑塞进 provides 是对机制的误用。

















