1. 概述
本文将介绍如何通过 Maven 运行 Java 代码时传递 JVM 参数。我们先探讨如何全局应用这些参数,然后重点说明如何为特定 Maven 插件配置参数。
2. 设置全局 JVM 参数
首先介绍两种为 Maven 主进程设置 JVM 参数的技巧。
2.1. 使用命令行
要通过 JVM 参数运行 Java Maven 项目,需设置 MAVEN_OPTS
环境变量。 该变量包含 JVM 启动时使用的参数,允许我们传递额外选项:
$ export MAVEN_OPTS="-Xms256m -Xmx512m"
本例中,我们通过 MAVEN_OPTS
定义了最小和最大堆内存大小。随后可通过以下命令执行构建:
$ mvn clean install
这些参数将应用于构建的主进程。
2.2. 使用 jvm.config
文件
自动设置全局 JVM 参数的替代方案是定义 jvm.config
文件。该文件必须位于项目根目录的 .mvn
文件夹中。文件内容即为要应用的 JVM 参数。例如,要模拟之前使用的命令行配置,配置文件内容应为:
-Xms256m -Xmx512m
3. 为特定插件设置 JVM 参数
Maven 插件可能 fork 新的 JVM 进程来执行任务,因此全局参数设置对它们无效。每个插件都有自己设置参数的方式,但大多数配置类似。我们将展示三个常用插件的示例:spring-boot Maven 插件、surefire 插件和 failsafe 插件。
3.1. 示例搭建
我们将搭建一个基础 Spring 项目,但要求代码仅在设置特定 JVM 参数时才能运行。
首先编写服务类:
@Service
class MyService {
int getLength() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
ArrayList<String> arr = new ArrayList<>();
Field sizeField = ArrayList.class.getDeclaredField("size");
sizeField.setAccessible(true);
return (int) sizeField.get(arr);
}
}
Java 9 引入的模块系统对反射访问增加了更多限制。这段代码在 Java 8 中可编译运行,但在 Java 9 及更高版本中需要添加 --add-open
JVM 选项来开放 java-base
模块的 java-util
包的反射权限。
现在添加一个简单的控制器类:
@RestController
class MyController {
private final MyService myService;
public MyController(MyService myService) {
this.myService = myService;
}
@GetMapping("/length")
Integer getLength() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
return myService.getLength();
}
}
最后添加主应用类:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
3.2. Spring-Boot Maven 插件
添加 Maven spring-boot 插件的最新版本基础配置:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.3.0</version>
</plugin>
通过以下命令运行项目:
$ mvn spring-boot:run
应用启动成功,但当尝试访问 http://localhost:8080/length
时,会报错并出现以下日志:
java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.util.ArrayList.size accessible: module java.base does not "opens java.util" to unnamed module
如前所述,必须添加反射相关的 JVM 参数来避免此错误。对于 spring-boot Maven 插件,需使用 jvmArguments
配置标签:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<jvmArguments>--add-opens java.base/java.util=ALL-UNNAMED</jvmArguments>
</configuration>
</plugin>
重新运行项目后,访问 http://localhost:8080/length
将返回预期值 0
。
3.3. Surefire 插件
首先为服务添加单元测试:
class MyServiceUnitTest {
MyService myService = new MyService();
@Test
void whenGetLength_thenZeroIsReturned() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
assertEquals(0, myService.getLength());
}
}
Surefire 插件常用于通过 Maven 运行单元测试。先尝试添加插件的最新版本基础配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<includes>
<include>**/*UnitTest.java</include>
</includes>
</configuration>
</plugin>
通过 Maven 运行单元测试的最简命令:
$ mvn test
测试失败,报错与之前相同!但 surefire 插件的修正方式是设置 argLine
配置标签:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<includes>
<include>**/*UnitTest.java</include>
</includes>
<argLine>--add-opens=java.base/java.util=ALL-UNNAMED</argLine>
</configuration>
</plugin>
现在单元测试可以成功运行!
3.4. Failsafe 插件
为控制器编写集成测试:
@SpringBootTest(classes = MyApplication.class)
@AutoConfigureMockMvc
class MyControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void whenGetLength_thenZeroIsReturned() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/length"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("0"));
}
}
通常使用failsafe 插件运行集成测试。再次配置其最新版本:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<includes>
<include>**/*IntegrationTest.java</include>
</includes>
</configuration>
</plugin>
运行集成测试:
$ mvn verify
不出意外,集成测试因同样的错误失败。failsafe 插件的修正方式与 surefire 插件相同。必须使用 argLine
配置标签设置 JVM 参数:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<includes>
<include>**/*IntegrationTest.java</include>
</includes>
<argLine>--add-opens=java.base/java.util=ALL-UNNAMED</argLine>
</configuration>
</plugin>
现在集成测试将成功运行。
4. 总结
本文介绍了在 Maven 中运行 Java 代码时使用 JVM 参数的方法。我们探讨了两种设置全局构建参数的技巧,然后展示了如何为最常用的插件传递 JVM 参数。虽然示例不全面,但其他插件的配置方式通常类似。要了解具体配置方式,建议查阅插件文档。
完整代码可在 GitHub 获取。