1. 概述
Java提供了与环境变量交互的简单方式,我们可以轻松读取它们,但修改却并不容易。然而在某些场景下(尤其是测试环境),我们需要对环境变量进行动态控制。
本文将探讨如何通过编程方式设置或修改环境变量。重点说明:本文仅讨论测试场景下的使用。在业务逻辑中滥用动态环境变量容易引发问题,应尽量避免。
2. 读取环境变量
读取环境变量的操作非常直接。System
类提供了相关功能:
@Test
void givenOS_whenGetPath_thenVariableIsPresent() {
String classPath = System.getenv("PATH");
assertThat(classPath).isNotNull();
}
若需获取所有环境变量:
@Test
void givenOS_whenGetEnv_thenVariablesArePresent() {
Map<String, String> environment = System.getenv();
assertThat(environment).isNotNull();
}
⚠️ 注意:System
类未提供修改方法,且返回的Map
是不可修改的。
3. 修改环境变量
根据进程层级关系,修改环境变量有三种场景:
- 子进程修改父进程的环境变量(不讨论)
- 进程修改自身环境变量
- 父进程修改子进程的环境变量
我们只关注后两种场景。第一种情况过于复杂且通常需要C/C++实现,本文仅讨论纯Java解决方案。
4. 修改当前进程环境变量
4.1. 使用反射API
通过反射可以强制修改System
类的内部实现:
@SuppressWarnings("unchecked")
private static Map<String, String> getModifiableEnvironment()
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class<?> environmentClass = Class.forName(PROCESS_ENVIRONMENT);
Field environmentField = environmentClass.getDeclaredField(ENVIRONMENT);
assertThat(environmentField).isNotNull();
environmentField.setAccessible(true);
Object unmodifiableEnvironmentMap = environmentField.get(STATIC_METHOD);
assertThat(unmodifiableEnvironmentMap).isNotNull();
assertThat(unmodifiableEnvironmentMap).isInstanceOf(UMODIFIABLE_MAP_CLASS);
Field underlyingMapField = unmodifiableEnvironmentMap.getClass().getDeclaredField(SOURCE_MAP);
underlyingMapField.setAccessible(true);
Object underlyingMap = underlyingMapField.get(unmodifiableEnvironmentMap);
assertThat(underlyingMap).isNotNull();
assertThat(underlyingMap).isInstanceOf(MAP_CLASS);
return (Map<String, String>) underlyingMap;
}
❌ 踩坑提醒:此方法在Java 9+会破坏模块边界:
java.lang.reflect.InaccessibleObjectException:
Unable to make field private static final java.util.Map java.lang.ProcessEnvironment.theUnmodifiableEnvironment accessible:
module java.base does not "opens java.lang" to unnamed module @2c9f9fb0
需添加JVM参数解决:
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
✅ 推荐方案:使用JUnit Pioneer库简化操作:
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
声明式设置环境变量:
@Test
@SetEnvironmentVariable(key = ENV_VARIABLE_NAME, value = ENV_VARIABLE_VALUE)
void givenVariableSet_whenGetEnvironmentVariable_thenReturnsCorrectValue() {
String actual = System.getenv(ENV_VARIABLE_NAME);
assertThat(actual).isEqualTo(ENB_VARIABLE_VALUE);
}
4.2. JNI方案
通过JNI调用C/C++代码修改环境变量:
- ⚠️ 缺点:需要C/C++技能,且可能无法更新Java运行时缓存
- ❌ 跨平台问题:不同操作系统实现差异大
- ✅ 优势:不涉及反射访问限制
5. 修改子进程环境变量
使用ProcessBuilder
创建子进程并设置环境变量:
@Test
void givenChildProcessTestRunner_whenRunTheTest_thenAllSucceed()
throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.inheritIO();
Map<String, String> environment = processBuilder.environment();
environment.put(CHILD_PROCESS_CONDITION, CHILD_PROCESS_VALUE);
environment.put(ENVIRONMENT_VARIABLE_NAME, ENVIRONMENT_VALUE);
Process process = processBuilder.command(arguments).start();
int errorCode = process.waitFor();
assertThat(errorCode).isZero();
}
通过Maven执行指定测试:
public static final String CHILD_PROCESS_TAG = "child_process";
public static final String TAG = String.format("-Dgroups=%s", CHILD_PROCESS_TAG);
private final String testClass = String.format("-Dtest=%s", getClass().getName());
private final String[] arguments = {"mvn", "test", TAG, testClass};
标记测试用例:
@Test
@EnabledIfEnvironmentVariable(named = CHILD_PROCESS_CONDITION, matches = CHILD_PROCESS_VALUE)
@Tag(CHILD_PROCESS_TAG)
void givenChildProcess_whenGetEnvironmentVariable_thenReturnsCorrectValue() {
String actual = System.getenv(ENVIRONMENT_VARIABLE_NAME);
assertThat(actual).isEqualTo(ENVIRONMENT_VALUE);
}
6. Docker环境方案
对于复杂测试环境,推荐使用Docker+Testcontainers:
Dockerfile示例:
FROM maven:3.9-amazoncorretto-17
WORKDIR /app
COPY /src/test/java/com/baeldung/setenvironment/SettingDockerEnvironmentVariableUnitTest.java \
./src/test/java/com/baeldung/setenvironment/
COPY /docker-pom.xml ./
ENV CUSTOM_DOCKER_ENV_VARIABLE=TRUE
ENTRYPOINT mvn -f docker-pom.xml test
Testcontainers集成示例:
class SettingTestcontainerVariableUnitTest {
public static final String CONTAINER_REPORT_FILE = "/app/target/surefire-reports/TEST-com.baeldung.setenvironment.SettingDockerEnvironmentVariableUnitTest.xml";
public static final String HOST_REPORT_FILE = "./container-test-report.xml";
public static final String DOCKERFILE = "./Dockerfile";
@Test
void givenTestcontainerEnvironment_whenGetEnvironmentVariable_thenReturnsCorrectValue() {
Path dockerfilePath = Paths.get(DOCKERFILE);
GenericContainer container = new GenericContainer(
new ImageFromDockerfile().withDockerfile(dockerfilePath));
assertThat(container).isNotNull();
container.start();
while (container.isRunning()) {
// Busy spin
}
container.copyFileFromContainer(CONTAINER_REPORT_FILE, HOST_REPORT_FILE);
}
}
⚠️ 注意:容器文件复制功能有限,建议使用withFileSystemBind()
或Dockerfile直接配置挂载。
7. 总结
Java允许直接操作环境变量,但动态修改存在挑战:
- ✅ 测试场景:反射/子进程/Docker方案各有适用场景
- ❌ 业务逻辑:滥用环境变量通常违反SOLID原则
- ✅ 推荐方案:优先考虑子进程或Docker方案,反射方案需谨慎处理模块访问问题
所有示例代码可在GitHub仓库获取。