requires static的核心目标是解耦编译期类型引用与运行时模块强制存在,确保编译通过且运行时可选——如SLF4J API编译必需、实现模块运行可无,配合uses和ServiceLoader实现安全插拔。

用 requires static 声明可选依赖,核心目标不是“减少包体积”本身,而是解耦编译期类型引用与运行时模块强制存在之间的绑定关系。它不直接删代码、不压缩 JAR,但能让你在保留扩展能力的同时,避免把插件类、调试工具、非主干功能的模块硬塞进最终部署包中。
明确哪些场景适合 requires static
这类依赖的本质是:编译时必须看见类型定义(否则编译失败),但运行时可以没有——只要你的代码逻辑能安全绕过它们。典型适用情况包括:
- 日志门面(如 SLF4J API):编译需要
Logger类型,但运行时可用 JUL、Log4j2 或不加载任何实现 - 插件接口定义(如
Processor、ExtensionPoint):主模块声明服务契约,扩展模块按需提供,不部署时不影响启动 - 测试/诊断辅助类(如 JUnit 扩展、Micrometer 指标桥接):仅在特定环境启用,生产镜像里不打包对应实现模块
module-info.java 中的正确写法
关键不是加不加 static,而是搭配 uses 或运行时条件判断,形成闭环。例如:
module com.example.service {
requires java.base;
requires static org.slf4j; // 编译期需要 API,运行期不要求实现
uses org.slf4j.spi.SLF4JServiceProvider; // 声明 SPI,让 JVM 在有实现时自动发现
}
注意:requires static 本身不触发类加载;真正决定是否加载实现模块的是 uses + ServiceLoader 调用时机,或你手动写的 Class.forName(...) 包裹在 try-catch 中。
配合构建工具控制实际打包内容
Java 模块系统只管“能否加载”,不管“是否打进最终产物”。要真正降低运行时包体积,需在构建阶段剔除未使用的模块:
- 使用
jlink构建自定义运行时镜像时,只包含--add-modules明确列出的模块,requires static的模块默认不会被递归加入 - Gradle/Maven 构建中,把可选依赖设为
compileOnly(Maven 的provided)或通过 profile 控制 scope,确保它们不出现在 runtime classpath/module-path - Android 项目中,若用到 Jetpack 组件的可选集成(如
androidx.benchmark),应放在debugImplementation下,而非implementation,避免打入 release APK
验证是否生效的关键检查点
光写对 module-info.java 不够,必须确认三点:
- 编译能过:引入了
org.slf4j.Logger等类型,且没报“package not visible” - 运行时不报
NoClassDefFoundError:删除对应实现模块(如 slf4j-simple.jar)后,应用仍能启动(前提是没执行到实际调用日志的路径) - 运行时行为可控:通过
ServiceLoader.load()返回空列表,或用Optional.ofNullable(...)封装可选逻辑,避免 NPE 或硬崩溃











