1. Overview
1.概述
Maven is an integral tool for the majority of Java projects. It provides a convenient way to run and configure the build. However, in some cases, we need more control over the process. Running a Maven build from Java makes it more configurable, as we can make many decisions at runtime.
Maven 是大多数 Java 项目不可或缺的工具。它提供了一种运行和配置构建的便捷方法。然而,在某些情况下,我们需要对流程进行更多控制。从 Java 运行 Maven 构建使其更具可配置性,因为我们可以在运行时做出许多决定。
In this tutorial, we’ll learn how to interact with Maven and run builds directly from the code.
在本教程中,我们将学习如何与 Maven 交互,并直接从代码中运行构建。
2. Learning Platform
2.学习平台
Let’s consider the following example to better understand the goal and usefulness of working with Maven directly from Java: Imagine a Java learning platform where students can choose from various topics and work on assignments.
让我们看看下面的例子,以便更好地理解直接从 Java 使用 Maven 的目标和实用性:试想一个 Java 学习平台,学生可以从各种主题中进行选择并完成作业。
Because our platform mainly targets beginners, we want to streamline the entire experience as much as possible. Thus, students can choose any topic they want or even combine them. We generate the project on the server, and students complete it online.
由于我们的平台主要面向初学者,我们希望尽可能简化整个体验。因此,学生可以选择他们想要的任何主题,甚至可以将它们组合在一起。我们在服务器上生成项目,学生在线完成。
To generate a project from scratch, we’ll be using a maven-model library from Apache:
为了从头开始生成一个项目,我们将使用 Apache 提供的 maven-model 库:
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>3.9.6</version>
</dependency>
Our builder will take simple steps to create a POM file with the initial information:
我们的生成器将采取简单的步骤,创建一个包含初始信息的 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
}
First, we ensure that all students have the correct environment. Second, we reduce the steps they need to take, from getting an assignment to starting coding. Setting up an environment might be trivial, but dealing with dependency management and configuration before writing their first “Hello World” program might be too much for beginners.
首先,我们确保所有学生都有正确的环境。其次,我们减少了他们从获得任务到开始编码的步骤。设置环境可能是小事一桩,但在编写第一个 “Hello World “程序之前处理依赖关系管理和配置,对初学者来说可能就太麻烦了。
Also, we want to introduce a wrapper that would interact with Maven from Java:
此外,我们还想引入一个封装器,从 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);
}
For now, this wrapper would only compile the project. However, we can extend it with additional operations.
目前,这个封装器只能编译项目。不过,我们可以用其他操作来扩展它。
3. Universal Executors
3.通用执行人
Firstly, let’s check the tool where we can run simple scripts. Thus, the solution isn’t specific to Maven, but we can run mvn commands. We have two options: Runtime.exec and ProcessBuilder. They are so similar that we can use an additional abstract class to handle exceptions:
首先,让我们检查一下可以运行简单脚本的工具。因此,该解决方案并非针对 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 Executor
3.1.运行时执行器
Let’s check how we can run a simple command with Runtime.exec(String[]):
让我们看看如何使用 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();
}
}
This is quite a straightforward approach for any scripts and commands we need to run from Java.
对于我们需要从 Java 运行的任何脚本和命令来说,这是一种相当直接的方法。
3.2. Process Builder
3.2.流程生成器
Another option is ProcessBuilder. It’s similar to the previous solution but provides a slightly better API:
另一个选择是 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();
}
}
From Java 9, ProcessBuilder can use pipelines that look similar to Streams. This way, we can run the build and trigger additional processing.
在 Java 9 中,ProcessBuilder 可以使用 管道,该管道与 Streams 类似。这样,我们就可以运行构建并触发额外的处理。
4. Maven APIs
4.Maven API
Now, let’s consider the solution that is tailored for Maven. There are two options: MavenEmbedder and MavenInvoker.
现在,让我们考虑一下为 Maven 量身定制的解决方案。有两个选项:MavenEmbedder和MavenInvoker。
4.1. MavenEmbedder
4.1 MavenEmbedder.
While previous solutions didn’t require any additional dependencies, for this one, we need to use the following package:
虽然以前的解决方案不需要任何额外的依赖项,但在这个解决方案中,我们需要使用 下面的软件包:
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>3.9.6</version>
</dependency>
This library provides us with a high-level API and simplifies the interaction with Maven:
该库为我们提供了高级应用程序接口,简化了与 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
4.2.MavenInvoker
Another tool similar to MavenEmbedder is MavenInvoker. To use it, we also need to import a library:
另一个与 MavenEmbedder 类似的工具是 MavenInvoker。要使用它,我们还需要 导入一个库:
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-invoker</artifactId>
<version>3.2.0</version>
</dependency>
It also provides a nice high-level API for interaction:
它还提供了一个很好的高级交互应用程序接口:
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. Testing
5.测试
Now, we can ensure that we create and compile a project:
现在,我们可以确保创建并编译一个项目:
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());
}
}
We generated an object from scratch and compiled it directly from Java code. Although we don’t encounter such requirements daily, automating Maven processes may benefit some projects.
我们从头开始生成了一个对象,并直接从 Java 代码中编译了它。虽然我们并不是每天都会遇到这样的需求,但自动化 Maven 流程可能会使某些项目受益。
6. Conclusion
6.结论</b
Maven configure and build a project based on a POM file. However, the XML configuration doesn’t work well with dynamic parameters and conditional logic.
Maven 基于 POM 文件配置和构建项目。但是,XML 配置不能很好地处理动态参数和条件逻辑。
We can leverage Java code to set up a Maven build by running it directly from the code. The best way to achieve this is to use specific libraries, like MavenEmbedder or MavenInvoker. At the same time, there are several, more low-level methods to get a similar result.
我们可以利用 Java 代码来设置 Maven 构建,直接从代码中运行它。实现这一目标的最佳方法是使用特定的库,如 MavenEmbedder 或 MavenInvoker。同时,还有几种更低级的方法可以获得类似的结果。
As usual, all the code from this tutorial is available over on GitHub.
与往常一样,本教程中的所有代码都可以在 GitHub 上获取。