provides 是Java模块系统中声明本模块实现并公开某服务接口的关键机制,不传递具体变量值,而是通过接口抽象、多实现及ServiceLoader运行时加载共同支撑动态变量注入。

要构建支持动态变量注入的系统,关键不是“把变量塞进去”,而是让模块系统明确知道:谁提供服务、谁消费服务、服务契约是否匹配。provides 声明正是这个机制的核心一环——它不负责传值,而是声明“本模块实现了某个接口,并愿意向其他模块公开该实现”。
provides 的定位与前提条件
provides 出现在 module-info.java 中,必须配合 uses(消费者声明)和一个标准服务接口使用。它本身不携带数据或变量,只表达“我实现了 X 接口”。例如:
-
provides java.sql.Driver with com.mysql.cj.jdbc.Driver;→ 表示本模块打包了 MySQL 驱动类,并将其注册为java.sql.Driver服务 - 前提是
com.mysql.cj.jdbc.Driver真正实现了java.sql.Driver接口,且该接口在模块路径上可访问 - 若接口来自未模块化的库(如老版本 commons-lang),JVM 会将其转为自动模块,但无法保证其导出该接口包——此时 provides 会编译失败或运行时报
NoClassDefFoundError
动态变量注入的实际落地方式
所谓“动态变量”,通常指配置项、环境标识、运行时开关等。provides 本身不传字符串或数字,但可通过服务接口封装行为逻辑:
- 定义一个轻量接口,如
ConfigSource,含方法String get(String key)或<t> T get(String key, Class<t> type)</t></t> - 编写多个实现类:
SystemPropertyConfigSource(读 system properties)、EnvVarConfigSource(读环境变量)、YamlFileConfigSource(解析配置文件) - 在各自模块的 module-info.java 中分别声明:
provides ConfigSource with SystemPropertyConfigSource;provides ConfigSource with EnvVarConfigSource; - 主模块用
uses ConfigSource;,并在运行时通过ServiceLoader.load(ConfigSource.class)获取全部可用实现,按优先级顺序尝试获取变量值
避免常见陷阱的操作要点
provides 不是万能胶,用错场景反而引发不可控行为:
- 不要试图用 provides 注入单个 String 变量(如
provides String with "prod")——Java 模块系统不支持对基础类型或非接口/抽象类做服务注册 - 确保所有 provides 实现类的构造函数是 public 且无参,否则
ServiceLoader无法实例化 - 若多个模块提供了同一接口,JVM 加载顺序不确定;需在代码中显式排序(如按模块名、加 @Priority 注解、或约定命名规则)
- 测试时务必启用
--show-module-resolution启动参数,确认你的 provides 模块确实被识别并参与服务发现
真正支撑动态性的,是接口抽象 + 多实现 + 运行时加载这一整套协作机制。provides 是其中让“谁有实现”变得可声明、可验证、可约束的关键声明点。










