1. 构建模块化项目:基本原则
编译器与模块路径(module-path)
在模块出现之前,整个应用程序是通过所谓的 classpath 编译和运行的——它只是 Java 查找类的目录和 JAR 文件列表。在模块系统中出现了一个新角色——module-path。现在,编译器和 JVM 不仅需要知道类在哪里,还需要知道它们属于哪些模块、有哪些依赖,以及哪些包被导出。
关键点:
- 对于模块化项目,请使用 --module-path 而不是 -classpath。
- 编译器和 JVM 必须能看到应用所需的所有模块(以及它们的依赖)。
示例:手动编译模块
假设你有两个模块:core 和 app,其中 app 依赖 core。
目录结构:
project-root/
core/
src/
main/
java/
module-info.java
com/example/core/...
app/
src/
main/
java/
module-info.java
com/example/app/...
手动编译(演示):
# 先编译 core
javac -d out/core core/src/main/java/module-info.java core/src/main/java/com/example/core/*.java
# 然后编译 app,并把 module-path 指向 core
javac --module-path out/core -d out/app app/src/main/java/module-info.java app/src/main/java/com/example/app/*.java
运行:
java --module-path out/core:out/app -m app/com.example.app.Main
在真实项目中无需手动执行这些步骤——请使用 Maven/Gradle 或 IDE。
2. Maven:构建多模块项目
Maven 中的多模块项目结构
Maven 早已支持包含多个模块(submodules)的结构。随着 Java 模块系统的出现,这些 “模块” 在 Maven 中往往与 Java 模块对应,但并非总是如此:Maven 模块只是一个子项目,而 Java 模块是由 module-info.java 描述的。通常一个 Maven 模块 = 一个 Java 模块。
典型结构:
myproject/
pom.xml # 父 POM
core/
pom.xml # 模块 core
src/main/java/
module-info.java
com/example/core/...
app/
pom.xml # 模块 app
src/main/java/
module-info.java
com/example/app/...
Parent POM 示例(myproject/pom.xml):
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>core</module>
<module>app</module>
</modules>
</project>
core 模块的 POM 示例(core/pom.xml):
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>core</artifactId>
<dependencies>
<!-- 这里可以声明依赖,例如其他模块或第三方库 -->
</dependencies>
</project>
app 模块的 POM 示例(app/pom.xml):
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>app</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
重要:
- 如果在 app 中需要 core,则在 app 模块的 module-info.java 中必须写上 requires core;
- 只要正确声明了 <dependency> 和 <module>,Maven 会自动解析并构建依赖。
Maven 如何处理 module-info.java
- Maven 会自动与其他类一起编译 module-info.java。
- 如果存在模块化依赖,Maven 会在编译与运行时将它们加入 module-path。
- 如果使用过旧的插件或低于 9 的 Java 版本,模块系统将无法工作。
通过 Maven 运行模块化应用
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<release>21</release> <!-- 或你的 Java 版本 -->
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>com.example.app.Main</mainClass>
<commandlineArgs></commandlineArgs>
</configuration>
</plugin>
</plugins>
</build>
mvn clean install
mvn -pl app exec:java
使用 Maven 与模块时的常见错误
错误:“module not found”。未正确指定 module-path,或没有为依赖的构件声明 <dependency>。
模块名重复。项目中不应存在两个同名模块(例如两个 module core)。
缺少 export/requires。若忘记导出包(exports)或声明依赖(requires),会出现编译错误。
混用 classpath 与 module-path。不要使用不支持模块的过时插件/配置,否则会导致不可预测的错误。
3. Gradle:构建多模块项目
Gradle 项目结构
myproject/
settings.gradle
build.gradle
core/
build.gradle
src/main/java/module-info.java
src/main/java/com/example/core/...
app/
build.gradle
src/main/java/module-info.java
src/main/java/com/example/app/...
settings.gradle
rootProject.name = 'myproject'
include 'core', 'app'
build.gradle(根项目)
subprojects {
apply plugin: 'java'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21) // 或你的 Java 版本
}
}
repositories {
mavenCentral()
}
}
core 的 build.gradle
plugins {
id 'java'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
app 的 build.gradle
plugins {
id 'java'
id 'application'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
dependencies {
implementation project(':core')
}
application {
mainModule = 'app' // 你的 Java 模块名
mainClass = 'com.example.app.Main'
}
构建与运行
./gradlew build
./gradlew :app:run
如果检测到 module-info.java,Gradle 会自动推断 module-path。若仍无法工作,请检查你使用的是较新的 Gradle 及其插件版本。
4. IntelliJ IDEA:创建与配置模块化项目
创建模块化项目
- File → New → Project → Java → 勾选“Create module-info.java”。
- 对于多模块项目:File → New → Module —— 向项目中添加新模块。
- 如果使用 Maven/Gradle —— 直接通过 pom.xml 或 build.gradle 导入项目即可。
IDEA 中的结构
- 在 Project View 中,每个模块都会显示为一条独立分支。
- 每个模块都有自己的 module-info.java 和源代码。
- IDEA 会提示哪些模块导出/需要哪些包(exports/requires)。
- 如果忘记导出包或声明依赖——IDEA 会高亮错误。
检查 module-path,运行 main 类
- 在 Run/Debug Configurations 中指定:
- Main class(例如,com.example.app.Main)
- Module(例如,app)
- IDEA 会自动处理 module-path。
- 如果通过 Maven/Gradle 启动——使用相应的运行配置(例如,app [run])。
导入第三方模块与库
- 使用外部库时(例如,requires java.sql; 或第三方 JAR)——通过 Maven/Gradle 添加依赖。
- 如果库不是模块化的(没有 module-info.java),它会自动被放入“unnamed module”(对大多数库来说这没问题)。
5. 实操:创建一个简单的多模块项目
步骤 1. 创建项目结构
myproject/
core/
src/main/java/module-info.java
src/main/java/com/example/core/HelloService.java
app/
src/main/java/module-info.java
src/main/java/com/example/app/Main.java
HelloService.java (core):
package com.example.core;
public class HelloService {
public String getHello() {
return "来自 core 模块的问候!";
}
}
module-info.java (core):
module core {
exports com.example.core;
}
Main.java (app):
package com.example.app;
import com.example.core.HelloService;
public class Main {
public static void main(String[] args) {
HelloService service = new HelloService();
System.out.println(service.getHello());
}
}
module-info.java (app):
module app {
requires core;
}
步骤 2. 通过 Maven 构建并运行
在项目根目录创建 parent pom.xml 和两个子项目(core/pom.xml、app/pom.xml),如上所示。在 app/pom.xml 中添加对 core 的依赖。
mvn clean install
mvn -pl app exec:java
步骤 3. 通过 Gradle 构建并运行
创建 settings.gradle、根 build.gradle,并为每个模块各建一个。在 app/build.gradle 中声明对 core 的依赖和 mainClass。
./gradlew build
./gradlew :app:run
步骤 4. 在 IntelliJ IDEA 中导入并运行
- 通过 File → Open 打开项目根目录。
- IDEA 会识别 Maven/Gradle 结构并创建模块。
- 在每个模块中打开 module-info.java,IDEA 会展示模块之间的关系。
- 通过上下文菜单运行 Main.java(“Run Main.main()”)——IDEA 会自动设置 module-path。
6. 构建模块化项目的常见错误
错误 1:module-path 不正确。如果在运行或编译时出现类似“module not found”或“package is not visible”的信息,很可能是没有正确指定 module-path,或忘记在 pom.xml/build.gradle 中添加依赖。
错误 2:模块名重复。项目中不应出现两个同名模块(例如两个 module core)。这会导致编译错误。
错误 3:未导出的包。如果类位于未通过 exports 在 module-info.java 中导出的包中,则其他模块将无法访问该类,即使它是 public。尝试使用该类时会出现编译错误。
错误 4:缺少 requires 声明。如果模块使用了另一个模块的类,但没有在 module-info.java 中写 requires,编译器会报错。
错误 5:与外部库不兼容。模块与没有 module-info.java 的库(它们会落入“unnamed module”)之间的“不匹配”通常无碍,但在某些配置下可能需要额外设置。
错误 6:从错误的模块启动。在 IDEA 或 Maven/Gradle 的运行配置中指定了错误的 main 类或模块——应用将无法启动。请检查 Main、mainModule/module 以及所用的 profile。
GO TO FULL VERSION