從 Java 程式碼運行 Maven
1. 概述
Maven 是大多數 Java 專案不可或缺的工具。它提供了一種運行和配置構建的便捷方法。然而,在某些情況下,我們需要對流程進行更多控制。從 Java 運行 Maven 建置使其更加可配置,因為我們可以在運行時做出許多決定。
在本教程中,我們將學習如何與 Maven 互動並直接從程式碼執行建置。
2. 學習平台
讓我們考慮以下範例,以便更好地理解直接從 Java 使用 Maven 的目標和用處:想像一個 Java 學習平台,學生可以在其中選擇各種主題並完成作業。
由於我們的平台主要針對初學者,因此我們希望盡可能簡化整個體驗。因此,學生可以選擇他們想要的任何主題,甚至可以將它們組合起來。我們在伺服器上產生項目,學生在線上完成。
要從頭開始產生項目,我們將使用 Apache 的maven-model
庫:
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>3.9.6</version>
</dependency>
我們的建構器將採取簡單的步驟來建立包含初始資訊的 POM 檔案:
public class ProjectBuilder {
// constants
public ProjectBuilder addDependency(String groupId, String artifactId, String version) {
Dependency dependency = new Dependency();
dependency.setGroupId(groupId);
dependency.setArtifactId(artifactId);
dependency.setVersion(version);
dependencies.add(dependency);
return this;
}
public ProjectBuilder setJavaVersion(JavaVersion version) {
this.javaVersion = version;
return this;
}
public void build(String userName, Path projectPath, String packageName) throws IOException {
Model model = new Model();
configureModel(userName, model);
dependencies.forEach(model::addDependency);
Build build = configureJavaVersion();
model.setBuild(build);
MavenXpp3Writer writer = new MavenXpp3Writer();
writer.write(new FileWriter(projectPath.resolve(POM_XML).toFile()), model);
generateFolders(projectPath, SRC_TEST);
Path generatedPackage = generateFolders(projectPath,
SRC_MAIN_JAVA +
packageName.replace(PACKAGE_DELIMITER, FileSystems.getDefault().getSeparator()));
String generatedClass = generateMainClass(PACKAGE + packageName);
Files.writeString(generatedPackage.resolve(MAIN_JAVA), generatedClass);
}
// utility methods
}
首先,我們確保所有學生都有正確的環境。其次,我們減少了他們需要採取的步驟,從獲得作業到開始編碼。設定環境可能很簡單,但在編寫第一個「Hello World」程式之前處理依賴關係管理和配置對於初學者來說可能有點困難。
另外,我們想引進一個可以從 Java 與 Maven 互動的包裝器:
public interface Maven {
String POM_XML = "pom.xml";
String COMPILE_GOAL = "compile";
String USE_CUSTOM_POM = "-f";
int OK = 0;
String MVN = "mvn";
void compile(Path projectFolder);
}
目前,這個包裝器只會編譯這個專案。但是,我們可以透過其他操作來擴展它。
3. 通用執行器
首先,讓我們檢查一下可以執行簡單腳本的工具。因此,該解決方案並非特定於 Maven,但我們可以執行mvn
命令。我們有兩個選擇: Runtime.exec
和ProcessBuilder.
它們非常相似,我們可以使用一個額外的抽象類別來處理異常:
public abstract class MavenExecutorAdapter implements Maven {
@Override
public void compile(Path projectFolder) {
int exitCode;
try {
exitCode = execute(projectFolder, COMPILE_GOAL);
} catch (InterruptedException e) {
throw new MavenCompilationException("Interrupted during compilation", e);
} catch (IOException e) {
throw new MavenCompilationException("Incorrect execution", e);
}
if (exitCode != OK) {
throw new MavenCompilationException("Failure during compilation: " + exitCode);
}
}
protected abstract int execute(Path projectFolder, String compileGoal)
throws InterruptedException, IOException;
}
3.1.運行時執行器
讓我們檢查一下如何使用Runtime.exec(String[])
來執行一個簡單的指令:
public class MavenRuntimeExec extends MavenExecutorAdapter {
@Override
protected int execute(Path projectFolder, String compileGoal) throws InterruptedException, IOException {
String[] arguments = {MVN, USE_CUSTOM_POM, projectFolder.resolve(POM_XML).toString(), COMPILE_GOAL};
Process process = Runtime.getRuntime().exec(arguments);
return process.waitFor();
}
}
對於我們需要從 Java 運行的任何腳本和命令來說,這是一個非常簡單的方法。
3.2.流程產生器
另一個選擇是ProcessBuilder
。它與先前的解決方案類似,但提供了稍微更好的 API:
public class MavenProcessBuilder extends MavenExecutorAdapter {
private static final ProcessBuilder PROCESS_BUILDER = new ProcessBuilder();
protected int execute(Path projectFolder, String compileGoal) throws IOException, InterruptedException {
Process process = PROCESS_BUILDER
.command(MVN, USE_CUSTOM_POM, projectFolder.resolve(POM_XML).toString(), compileGoal)
.start();
return process.waitFor();
}
}
從 Java 9 開始, ProcessBuilder
可以使用類似Streams
管道。這樣,我們就可以運行建置並觸發額外的處理。
4.Maven API
現在,讓我們考慮一下為 Maven 量身定制的解決方案。有兩個選項: MavenEmbedder
和MavenInvoker
。
4.1. MavenEmbedder
雖然先前的解決方案不需要任何額外的依賴項,但對於這個解決方案,我們需要使用以下套件:
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>3.9.6</version>
</dependency>
這個庫為我們提供了高階API並簡化了與Maven的互動:
public class MavenEmbedder implements Maven {
public static final String MVN_HOME = "maven.multiModuleProjectDirectory";
@Override
public void compile(Path projectFolder) {
MavenCli cli = new MavenCli();
System.setProperty(MVN_HOME, projectFolder.toString());
cli.doMain(new String[]{COMPILE_GOAL}, projectFolder.toString(), null, null);
}
}
4.2. MavenInvoker
另一個與MavenEmbedder
類似的工具是MavenInvoker
。要使用它,我們還需要導入一個庫:
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-invoker</artifactId>
<version>3.2.0</version>
</dependency>
它還提供了一個很好的高級 API 進行互動:
public class MavenInvoker implements Maven {
@Override
public void compile(Path projectFolder) {
InvocationRequest request = new DefaultInvocationRequest();
request.setPomFile(projectFolder.resolve(POM_XML).toFile());
request.setGoals(Collections.singletonList(Maven.COMPILE_GOAL));
Invoker invoker = new DefaultInvoker();
try {
InvocationResult result = invoker.execute(request);
if (result.getExitCode() != 0) {
throw new MavenCompilationException("Build failed", result.getExecutionException());
}
} catch (MavenInvocationException e) {
throw new MavenCompilationException("Exception during Maven invocation", e);
}
}
}
5. 測試
現在,我們可以確保創建並編譯一個專案:
class MavenRuntimeExecUnitTest {
private static final String PACKAGE_NAME = "com.baeldung.generatedcode";
private static final String USER_NAME = "john_doe";
@TempDir
private Path tempDir;
@BeforeEach
public void setUp() throws IOException {
ProjectBuilder projectBuilder = new ProjectBuilder();
projectBuilder.build(USER_NAME, tempDir, PACKAGE_NAME);
}
@ParameterizedTest
@MethodSource
void givenMavenInterface_whenCompileMavenProject_thenCreateTargetDirectory(Maven maven) {
maven.compile(tempDir);
assertTrue(Files.exists(tempDir));
}
static Stream<Maven> givenMavenInterface_whenCompileMavenProject_thenCreateTargetDirectory() {
return Stream.of(
new MavenRuntimeExec(),
new MavenProcessBuilder(),
new MavenEmbedder(),
new MavenInvoker());
}
}
我們從頭開始產生一個對象,並直接從 Java 程式碼編譯它。雖然我們每天都不會遇到這樣的需求,但自動化 Maven 流程可能會讓某些專案受益。
六,結論
Maven 根據 POM 檔案配置並建置專案。但是,XML 配置不能很好地處理動態參數和條件邏輯。
我們可以利用 Java 程式碼直接從程式碼運行來設定 Maven 建置。實現此目的的最佳方法是使用特定的庫,例如MavenEmbedder
或MavenInvoker
。同時,還有幾種更底層的方法可以得到類似的結果。
與往常一樣,本教程中的所有程式碼都可以在 GitHub 上取得。