如何从Java中设置环境变量?我发现我可以使用ProcessBuilder为子流程做到这一点。不过,我有几个子流程要启动,所以我宁愿修改当前流程的环境,让子流程继承它。
有一个System.getenv(String)用于获取单个环境变量。我还可以使用System.getenv()获得完整环境变量集的Map。但是,在该Map上调用put()会抛出UnsupportedOperationException——显然,它们意味着环境是只读的。并且,没有System.setenv()。
那么,有没有办法在当前运行的进程中设置环境变量呢?如果有,怎么做?如果不是,理由是什么?(是不是因为这是Java,所以我不应该做邪恶的、不可移植的、过时的事情,比如触摸我的环境?)如果不是,有什么好的建议来管理我将需要提供给几个子流程的环境变量更改吗?
在当前Java进程中设置环境变量,该进程使用依赖于所讨论的环境变量的本机代码(dll),只有在本机设置此环境变量时才有效。
这里的大多数示例都是在JVM中更改映射,但不能在本机工作。
我看到的一种方法是通过JNI,它可能也同样有效。
另一种方法是通过使用Kernel32接口(仅限windows)使用JNA平台。例如:
private static void setEnv(String key, String value) {
if(isWindows()) {
if (!Kernel32.INSTANCE.SetEnvironmentVariable(key, value)) {
System.err.println("Unable to set the environemnt variable: " + key);
}
}
}
对于类似unix的操作系统,可以使用LibCAPI接口,但还没有尝试过。
(是不是因为这是Java,所以我不应该做邪恶的、不可移植的、过时的事情,比如触摸我的环境?)
我认为你说到点子上了。
减轻负担的一个可能的方法是找出一种方法
void setUpEnvironment(ProcessBuilder builder) {
Map<String, String> env = builder.environment();
// blah blah
}
并在启动任何processbuilder之前通过它。
另外,您可能已经知道这一点,但是您可以使用同一个ProcessBuilder启动多个流程。因此,如果子流程是相同的,就不需要一遍又一遍地进行这个设置。
尝试了上面pushy的答案,它在很大程度上起了作用。然而,在某些情况下,我会看到这个例外:
java.lang.String cannot be cast to java.lang.ProcessEnvironment$Variable
由于ProcessEnvironment的某些内部类的实现,当该方法被多次调用时就会发生这种情况。如果setEnv(..)方法被多次调用,当从theEnvironment映射中检索键时,它们现在是字符串(在第一次调用setEnv(…)时已作为字符串放入),并且不能转换为映射的泛型类型Variable,这是ProcessEnvironment的私有内部类。
下面是一个固定版本(在Scala中)。希望将其应用到Java中并不太难。
def setEnv(newenv: java.util.Map[String, String]): Unit = {
try {
val processEnvironmentClass = JavaClass.forName("java.lang.ProcessEnvironment")
val theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment")
theEnvironmentField.setAccessible(true)
val variableClass = JavaClass.forName("java.lang.ProcessEnvironment$Variable")
val convertToVariable = variableClass.getMethod("valueOf", classOf[java.lang.String])
convertToVariable.setAccessible(true)
val valueClass = JavaClass.forName("java.lang.ProcessEnvironment$Value")
val convertToValue = valueClass.getMethod("valueOf", classOf[java.lang.String])
convertToValue.setAccessible(true)
val sampleVariable = convertToVariable.invoke(null, "")
val sampleValue = convertToValue.invoke(null, "")
val env = theEnvironmentField.get(null).asInstanceOf[java.util.Map[sampleVariable.type, sampleValue.type]]
newenv.foreach { case (k, v) => {
val variable = convertToVariable.invoke(null, k).asInstanceOf[sampleVariable.type]
val value = convertToValue.invoke(null, v).asInstanceOf[sampleValue.type]
env.put(variable, value)
}
}
val theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment")
theCaseInsensitiveEnvironmentField.setAccessible(true)
val cienv = theCaseInsensitiveEnvironmentField.get(null).asInstanceOf[java.util.Map[String, String]]
cienv.putAll(newenv);
}
catch {
case e : NoSuchFieldException => {
try {
val classes = classOf[java.util.Collections].getDeclaredClasses
val env = System.getenv()
classes foreach (cl => {
if("java.util.Collections$UnmodifiableMap" == cl.getName) {
val field = cl.getDeclaredField("m")
field.setAccessible(true)
val map = field.get(env).asInstanceOf[java.util.Map[String, String]]
// map.clear() // Not sure why this was in the code. It means we need to set all required environment variables.
map.putAll(newenv)
}
})
} catch {
case e2: Exception => e2.printStackTrace()
}
}
case e1: Exception => e1.printStackTrace()
}
}
我偶然发现了这个线程,因为我有一个类似的需求,我需要设置(或更新)一个环境变量(永久地)。
所以我研究了如何从命令提示符永久设置一个环境变量,这是非常简单的!
setx JAVA_LOC C:/Java/JDK
然后我在代码中实现了同样的功能
这是我使用的(假设- JAVA_LOC是env。变量名)
String cmdCommand = "setx JAVA_LOC " + "C:/Java/JDK";
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("cmd.exe", "/c", cmdCommand);
processBuilder.start();
ProcessBuilder启动cmd.exe并传递您想要的命令。
即使您关闭JVM/重新启动系统,环境变量也会保留,因为它与JVM/程序的生命周期没有关系。