Java 9中requires static用于声明可选依赖,编译时强制可见、运行时允许缺失、不传递依赖;需在module-info.java中正确声明,变量须延迟安全加载,测试时通过接口+显式注入配合。

Java 9 模块化系统中,static 修饰可选依赖是通过 requires static 实现的——它不是给变量加 static 关键字,而是修饰模块声明中的依赖关系,让该依赖在编译时必须存在、运行时可以缺失。
requires static 的核心作用
它解决的是“类型可用性”和“启动健壮性”的平衡问题:
- 编译期强制可见:确保你引用的接口、注解、配置类等能被
javac正确解析(比如你在代码里写了LoggerFactory.getLogger(...),就必须让 SLF4J 的 API 类型在编译时可访问) - 运行时允许缺失:JVM 启动不校验该模块是否存在,避免因未引入某实现(如 Logback)导致应用无法启动
- 不传递依赖:引用你的模块不会自动获得这个
requires static依赖,边界清晰
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;
}
注意三点:
立即学习“Java免费学习笔记(深入)”;
- 不能对
java.base使用requires static——它是隐式强依赖,语法非法 - 模块名必须准确;若依赖的是自动模块(如未声明
module-info.java的 JAR),requires static行为不稳定,应尽量避免 - 不要写成
requires static com.example.util这种自定义模块却标为 static——除非你明确设计它为可选,否则会削弱模块契约
可选依赖里的变量怎么安全加载
requires static 本身不初始化任何变量,也不触发类加载。真正需要使用的变量(如日志工厂、序列化器实例)必须主动、延迟、安全地初始化:
- 用
ServiceLoader.load(MySerializer.class)查找运行时存在的实现,再从中获取配置或单例 - 通过
Class.forName("com.fasterxml.jackson.databind.ObjectMapper", false, cl)检查类是否存在,捕获ClassNotFoundException后跳过相关逻辑 - 将变量声明为
private static volatile Serializer INSTANCE,配合双重检查锁 + 工厂方法初始化,避免静态块阻塞整个类加载 - 绝不在静态初始化块中做网络、IO 或耗时计算——失败会导致类初始化失败,后续所有使用都抛
NoClassDefFoundError
测试场景下的典型配合方式
想在测试时用 Mock 实现替代真实服务,不能靠 requires static 注入变量,但可以这样组织:
- 定义服务接口(如
MetricsReporter)并导出在 API 模块中 - 生产模块用
requires static声明对监控 SDK 的可选依赖,并提供默认实现 - 测试模块也声明
requires static对 Mock 库的依赖,并在测试类中通过构造器或 setter 显式传入 Mock 实例 - 主程序入口根据环境参数选择加载哪套实现,不依赖模块自动装配


















