Java里模块系统解决了什么问题_Java工程结构说明

模块系统通过显式声明依赖和导出解决JAR地狱问题,强制JVM启动时验证模块图;需正确配置module-info.java、模块路径及exports/opens指令。

模块系统解决了类路径混乱和隐式依赖问题

Java 9 引入的 module system 主要应对长期存在的“JAR 地狱”:同一类在多个 JAR 中重复出现、版本冲突、运行时才发现 NoClassDefFoundErrorIllegalAccessError。它强制显式声明依赖(requires)和导出(exports),让 JVM 在启动时就能验证模块图,而不是等到反射调用或类加载失败才暴露问题。

典型表现是:IDE 显示一切正常,但打包成 jar 后运行报 java.lang.module.FindException: Module xxx not found——这说明构建时没把模块加入 --module-path,或者 module-info.java 里漏写了 requires

标准 Java 模块工程结构必须包含 module-info.java

一个合法的命名模块(named module)必须在源码根目录(通常是 src/main/java)下有且仅有一个 module-info.java 文件。它不是可选配置,而是模块系统的入口契约。

常见错误包括:

  • module-info.java 放在 src/main/java/com/example/ 下(应放在 src/main/java/ 直接子目录)
  • 使用 Maven 构建时未设置 maven-compiler

    -plugin
    releasesource/target 为 9+
  • 模块名含大写字母或下划线(违反 Identifier 规则,如 my-module 合法,MyModule 不合法)
module com.example.app {
    requires java.base;
    requires javafx.controls;
    exports com.example.app.ui;
}

模块路径(--module-path)和类路径(-cp)不能混用

一旦用了 --module-path,JVM 就进入“模块模式”,此时 -cp(即 CLASSPATH)会被忽略——所有依赖必须作为模块显式提供。这也是为什么 Spring Boot 2.3+ 默认不支持 JPMS:它的 fat jar 是扁平化类路径结构,与模块系统互斥。

调试时常用组合:

  • java --module-path mods --module com.example.app/com.example.app.Main
  • 若依赖未命名模块(legacy JAR),需用 --add-modules 显式开启,例如:--add-modules java.xml.bind(Java 11 已移除)
  • 想临时开放模块内部包给非模块代码(如测试框架),用 --add-opens java.base/java.lang=ALL-UNNAMED

模块系统对传统分层架构的影响很实际

过去常见的 com.example.servicecom.example.repository 包可以同属一个模块,但若拆成两个模块(com.example.servicecom.example.data),就必须在 service 模块中写 requires com.example.data;,且 data 模块必须 exports com.example.data.api;(不能只 exports com.example.data; 内部实现包)。

这意味着:接口与实现分离不再是设计偏好,而是模块可见性的硬性要求。否则编译会报 error: package com.example.data.api is not visible

容易被忽略的一点是:模块名 ≠ 包名。一个模块可以导出多个不同前缀的包,也可以不导出任何包(仅作依赖中介)。真正起作用的是 exportsopens 指令,不是目录结构本身。