我正在尝试在从 Java 8 迁移到 Java 17 的 Java 应用程序中使用反射在运行时设置环境变量。以前,在 Java 8 中,我能够使用反射修改环境变量以访问集合中的底层映射.不可修改的Map。
但是,迁移到 Java 17 后,由于 Java 平台模块系统的强封装性,我收到以下错误:
java.lang.reflect.InaccessibleObjectException:无法使字段私有最终 java.util.Map java.util.Collections$UnmodifyingMap.m 可访问:模块 java.base 不会向未命名模块“打开 java.util”
虽然我知道我可以使用
--add-opens java.base/java.util=ALL-UNNAMED
作为解决方法,但我正在寻找一种不需要修改 JVM 参数的更好的解决方案。
我尝试使用在 Java 8 中有效、在 Kotlin 中实现的反射方法:
fun setEnv(newEnv: Map<String, String>) {
val unmodifiableMapClass = Collections.unmodifiableMap<Any, Any>(mapOf()).javaClass
with(unmodifiableMapClass.getDeclaredField("m")) {
isAccessible = true
@Suppress("UNCHECKED_CAST")
get(System.getenv()) as MutableMap<String, String>
}.apply {
clear()
putAll(newEnv)
}
}
我期望能够在运行时修改环境变量,但是 Java 17 的模块系统阻止访问这些内部实现并出现错误:
java.lang.reflect.InaccessibleObjectException:无法创建字段 私有最终 java.util.Map java.util.Collections$UnmodifyingMap.m 可访问:模块 java.base 不会向未命名的对象“打开 java.util” 模块
我想避免使用
--add-opens
标志,因为它感觉像是一种解决方法而不是正确的解决方案。我正在寻找一种适用于 Java 17 安全模型的干净替代方法。
对于单元测试,您可以专门使用一些实用程序来实现此目的。它们允许您使用指定环境变量的给定值来测试代码。例如,有Kotest系统扩展:
withEnvironment("FooKey", "BarValue") {
System.getenv("FooKey") shouldBe "BarValue" // System environment overridden!
}
如果您没有使用Kotest框架,您可以使用JUnit Pioneer:
@Test
@SetEnvironmentVariable(key = "some variable", value = "new value")
void testSet() {
assertThat(System.getenv("some variable")).isEqualTo("new value");
}