requires static 控制模块级依赖的编译可见性与运行可缺性,不触发变量初始化;可选模块中的变量需通过 ServiceLoader、类存在性检查或双重检查锁延迟安全初始化。

用 requires static 实现可选依赖,核心不是“加载变量”,而是让模块在编译时可见、运行时可缺——它控制的是模块级依赖的生命周期,不直接操作变量。真正需要预设或加载的变量,应结合静态初始化块、服务发现或工厂模式来完成。
理解 requires static 的真实作用范围
requires static 只作用于模块声明层面,影响的是模块图构建和类路径解析行为,它本身不触发任何变量初始化,也不执行任何代码。它的价值在于:
- 编译阶段强制存在:确保你调用的类型(如接口、注解、配置类)能被 javac 正确解析
- 运行阶段可缺失:JVM 启动时不校验该模块是否存在,避免因未引入某可选实现而启动失败
- 不传递依赖:它不会像
requires transitive那样把依赖“透传”给引用你的模块
可选依赖场景下变量如何安全初始化
当你依赖一个可选模块(比如日志实现、序列化器、监控探针),其中的配置变量或工具实例不能靠 requires static 自动加载,而需主动判断环境并延迟初始化:
- 用
ServiceLoader.load()查找可用服务实现,再从中提取配置对象或单例实例 - 检查类是否存在:
Class.forName("org.slf4j.LoggerFactory", false, cl),若抛ClassNotFoundException则跳过相关逻辑 - 将变量声明为
private static volatile ConfigData INSTANCE,配合双重检查锁 + 工厂方法初始化,避免静态块阻塞类加载 - 避免在静态初始化块中做 IO、网络或耗时计算——一旦失败,整个类不可用
module-info.java 中的典型写法与约束
假设你开发一个消息处理模块,支持对接不同序列化库(如 Jackson 或 Gson),但不强制绑定:
module com.example.message {
requires java.base;
requires static com.fasterxml.jackson.databind; // 编译时需有,运行时可无
requires static com.google.gson; // 同上
exports com.example.message.api;
}
注意三点:
- 不能写
requires static java.base——java.base是隐式强依赖,不允许加static - 若模块名是自动推导的(如未打包为命名模块的 JAR),
requires static会退化为对自动模块的弱引用,行为不稳定,应尽量避免 - 多个
requires static模块之间无加载顺序保证,变量初始化逻辑必须独立、幂等
运行时判断与兜底策略建议
有了 requires static 声明后,实际使用前仍需运行时确认:
- 通过
ModuleLayer.boot().modules()扫描当前模块图,过滤出目标模块是否存在 - 定义统一配置接口(如
SerializerProvider),由各可选模块提供provides实现,主模块用ServiceLoader加载首个可用者 - 为关键变量设置明确默认值(如
private static final int TIMEOUT_MS = 3000;),不依赖可选模块也能正常工作 - 日志中记录是否启用了某扩展能力,便于排查部署问题

















