如何从Java设置环境变量?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何从Java设置环境变量?相关的知识,希望对你有一定的参考价值。

如何从Java设置环境变量?我看到我可以使用ProcessBuilder为子进程执行此操作。我有几个子进程要启动,所以我宁愿修改当前进程的环境,让子进程继承它。

有一个System.getenv(String)用于获取单个环境变量。我还可以使用Map获取完整环境变量集的System.getenv()。但是,在put()上调用Map会抛出一个UnsupportedOperationException - 显然它们意味着环境只能被读取。并且,没有System.setenv()

那么,有没有办法在当前运行的进程中设置环境变量?如果是这样,怎么样?如果没有,理由是什么? (这是因为这是Java,因此我不应该做一些邪恶的非便携式过时的事情,比如触摸我的环境吗?)如果没有,那么管理环境变量的任何好建议都会改变我需要提供给几个子进程?

答案

(这是因为这是Java,因此我不应该做一些邪恶的非便携式过时的东西,比如触摸我的环境吗?)

我觉得你已经敲了敲头。

减轻负担的一种可能方法是分解出一种方法

void setUpEnvironment(ProcessBuilder builder) {
    Map<String, String> env = builder.environment();
    // blah blah
}

并在启动之前通过任何ProcessBuilders。

此外,您可能已经知道这一点,但您可以使用相同的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()
  }
}
另一答案

像大多数找到这个线程的人一样,我正在编写一些单元测试,需要修改环境变量来设置测试运行的正确条件。但是,我发现最受欢迎的答案有一些问题和/或非常神秘或过于复杂。希望这有助于其他人更快地解决问题。

首先,我终于发现@Hubert Grzeskowiak的解决方案是最简单的,它对我有用。我希望我能先来到那个。它基于@Edward Campbell的答案,但没有复杂的循环搜索。

但是,我开始使用@ pushy的解决方案,它得到了最多的赞成。它是@anonymous和@Edward Campbell的组合。 @pushy声称需要两种方法来涵盖Linux和Windows环境。我在OS X下运行,发现两者都有效(一旦@anonymous方法的问题得到修复)。正如其他人所指出的那样,这种解决方案大部分时间都有效,但不是全部。

我认为大多数混乱的根源来自@ anonymous在'theEnvironment'领域运行的解决方案。看一下ProcessEnvironment结构的定义,'theEnvironment'不是Map <String,String>,而是Map <Variable,Value>。清除地图工作正常,但putAll操作会重建Map <String,String>,这可能会导致后续操作使用期望Map <Variable,Value>的常规API对数据结构进行操作时出现问题。此外,访问/删除单个元素是一个问题。解决方案是通过'theUnmodifiableEnvironment'间接访问'theEnvironment'。但由于这是一个类型UnmodifiableMap,访问必须通过UnmodifiableMap类型的私有变量'm'完成。请参阅下面的代码中的getModifiableEnvironmentMap2。

在我的情况下,我需要为我的测试删除一些环境变量(其他变量应保持不变)。然后我想在测试后将环境变量恢复到先前的状态。下面的例程可以直截了当地做。我在OS X上测试了两个版本的getModifiableEnvironmentMap,两者都是等效的。虽然基于此主题中的注释,但根据环境的不同,可能是比另一个更好的选择。

注意:我没有包含对'theCaseInsensitiveEnvironmentField'的访问权限,因为这似乎是Windows特定的,我无法测试它,但添加它应该是直截了当的。

private Map<String, String> getModifiableEnvironmentMap() {
    try {
        Map<String,String> unmodifiableEnv = System.getenv();
        Class<?> cl = unmodifiableEnv.getClass();
        Field field = cl.getDeclaredField("m");
        field.setAccessible(true);
        Map<String,String> modifiableEnv = (Map<String,String>) field.get(unmodifiableEnv);
        return modifiableEnv;
    } catch(Exception e) {
        throw new RuntimeException("Unable to access writable environment variable map.");
    }
}

private Map<String, String> getModifiableEnvironmentMap2() {
    try {
        Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
        Field theUnmodifiableEnvironmentField = processEnvironmentClass.getDeclaredField("theUnmodifiableEnvironment");
        theUnmodifiableEnvironmentField.setAccessible(true);
        Map<String,String> theUnmodifiableEnvironment = (Map<String,String>)theUnmodifiableEnvironmentField.get(null);

        Class<?> theUnmodifiableEnvironmentClass = theUnmodifiableEnvironment.getClass();
        Field theModifiableEnvField = theUnmodifiableEnvironmentClass.getDeclaredField("m");
        theModifiableEnvField.setAccessible(true);
        Map<String,String> modifiableEnv = (Map<String,String>) theModifiableEnvField.get(theUnmodifiableEnvironment);
        return modifiableEnv;
    } catch(Exception e) {
        throw new RuntimeException("Unable to access writable environment variable map.");
    }
}

private Map<String, String> clearEnvironmentVars(String[] keys) {

    Map<String,String> modifiableEnv = getModifiableEnvironmentMap();

    HashMap<String, String> savedVals = new HashMap<String, String>();

    for(String k : keys) {
        String val = modifiableEnv.remove(k);
        if (val != null) { savedVals.put(k, val); }
    }
    return savedVals;
}

private void setEnvironmentVars(Map<String, String> varMap) {
    getModifiableEnvironmentMap().putAll(varMap);   
}

@Test
public void myTest() {
    String[] keys = { "key1", "key2", "key3" };
    Map<String, String> savedVars = clearEnvironmentVars(keys);

    // do test

    setEnvironmentVars(savedVars);
}
另一答案

这是Kotlin邪恶版的@ pushy的邪恶answer =)

@Suppress("UNCHECKED_CAST")
@Throws(Exception::class)
fun setEnv(newenv: Map<String, String>) {
    try {
        val processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment")
        val theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment")
        theEnvironmentField.isAccessible = true
        val env = theEnvironmentField.get(null) as MutableMap<String, String>
        env.putAll(newenv)
        val theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment")
        theCaseInsensitiveEnvironmentField.isAccessible = true
        val cienv = theCaseInsensitiveEnvironmentField.get(null) as MutableMap<String, String>
        cienv.putAll(newenv)
    } catch (e: NoSuchFieldException) {
        val classes = Collections::class.

以上是关于如何从Java设置环境变量?的主要内容,如果未能解决你的问题,请参考以下文章

如何在java程序设置环境变量

Android Java将变量从片段传递到活动[重复]

java开发的项目案例,大厂内部资料

Java进阶之光!2021必看-Java高级面试题总结

Java工程师面试题,二级java刷题软件

从外部存储中检索 Relay 查询片段的变量