Maven是大多数 Java 项目不可或缺的工具。它提供了一种运行和配置构建的便捷方法。然而,在某些情况下,我们需要对流程进行更多控制。从 Java 运行 Maven 构建使其更加可配置,因为我们可以在运行时做出许多决定。
在本教程中,我们将学习如何与 Maven 交互并直接从代码运行构建。
让我们考虑以下示例,以便更好地理解直接从 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); }
|
目前,这个包装器只会编译该项目。但是,我们可以通过其他操作来扩展它。通用执行器
首先,让我们检查一下可以运行简单脚本的工具。因此,该解决方案并非特定于 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; }
|
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 运行的任何脚本和命令来说,这是一种非常简单的方法。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 的管道。这样,我们就可以运行构建并触发额外的处理。Maven API
现在,让我们考虑一下为 Maven 量身定制的解决方案。有两个选项:MavenEmbedder和MavenInvoker。
1. Maven嵌入器
虽然以前的解决方案不需要任何额外的依赖项,但对于这个解决方案,我们需要使用以下包:
<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); } }
|
2. Maven调用者
另一个与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); } } }
|
测试
现在,我们可以确保创建并编译一个项目:
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。同时,还有几种更底层的方法可以获得类似的结果。