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 # Parent 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。
- 如果你使用舊版外掛或 Java 版本低於 9——模組系統將無法運作。
透過 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 與使用中的設定檔。
GO TO FULL VERSION