Java 运行时动态加载外部模块的核心是绕过编译期依赖检查,利用 ModuleLayer、ModuleFinder 和 Configuration 动态构建模块层,或通过 URLClassLoader 加载无 module-info.class 的传统 JAR,并辅以 --add-reads/--add-opens 启动参数打通模块边界。

Java 在运行时动态加载未在编译期声明的外部模块,核心是绕过编译期的模块依赖检查,利用 JVM 的类加载机制和模块系统(Java 9+)的动态操作能力。关键在于:不把模块写在 module-info.java 中声明 requires,而是通过 API 在运行时解析、定义并打开模块。
使用 ModuleLayer 和 ModuleFinder 动态构建模块层
Java 9 引入了模块系统,但允许在运行时创建新的 ModuleLayer,将外部 JAR 封装为模块并加入类路径之外的模块图中。
- 用
ModuleFinder.of(Path...)定位外部 JAR 文件(如plugin-a.jar) - 调用
Configuration.resolveAndBind()解析其module-info.class并处理依赖(可传入空配置以忽略未声明的依赖) - 用
ModuleLayer.defineModulesWithOneLoader()创建新层,指定类加载器(如自定义URLClassLoader) - 新模块中的类可通过
layer.findModule("module.name").get().getClassLoader().loadClass(...)加载
绕过 requires 声明:用 --add-reads 和 --add-opens 启动参数(适用于调试/脚本场景)
若无法修改主模块声明,可在启动 JVM 时强制打通模块边界:
-
--add-reads java.base=ALL-UNNAMED:让java.base模块“读取”你的动态模块(或ALL-UNNAMED表示所有未命名模块) -
--add-opens java.base/java.lang=ALL-UNNAMED:开放特定包给动态模块反射访问 - 注意:这些参数需在
java命令中显式指定,不能在运行时追加
加载无 module-info.class 的传统 JAR:走类路径 + URLClassLoader
很多外部模块仍是普通 JAR(无模块描述),此时不走模块系统,直接用类路径机制:
立即学习“Java免费学习笔记(深入)”;
- 创建
URLClassLoader,把 JAR 的file:///path/to/plugin.jar转为URL传入 - 调用
classLoader.loadClass("com.example.PluginService") - 通过反射或服务提供者机制(
ServiceLoader.load(..., classLoader))获取实例 - 该方式下类属于
unnamed module,默认可被任意模块读取(Java 9+ 中受限,需配合--add-reads)
结合 JPMS 和传统机制:用 ModuleLayer.defineModules() 加载 unnamed 模块
即使外部 JAR 没有 module-info.class,也能将其作为“自动模块(automatic module)”加入模块层:
- JAR 文件名决定模块名(如
guava-31.1-jre.jar→ 模块名guava) - 调用
ModuleFinder.of(jarPath)仍可找到它;Configuration.resolveAndBind()会生成自动模块描述 - 后续可对这个自动模块执行
addReads或addOpens(通过Module.addReads()等反射方式,需开启权限)


















