如何从Java设置环境变量?

问题

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

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

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


#1 热门回答(179 赞)

要在需要为单元测试设置特定环境值的场景中使用,你可能会发现以下hack非常有用。它将更改整个JVM中的环境变量(因此请确保在测试后重置所有更改),但不会改变你的系统环境。

我发现爱德华·坎贝尔和匿名的两个脏黑客的组合效果最好,因为其中一个在linux下不起作用,一个在Windows 7下不起作用。所以为了得到一个多平台的邪恶黑客,我把它们结合起来:

protected static void setEnv(Map<String, String> newenv) throws Exception {
  try {
    Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
    Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
    theEnvironmentField.setAccessible(true);
    Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
    env.putAll(newenv);
    Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
    theCaseInsensitiveEnvironmentField.setAccessible(true);
    Map<String, String> cienv = (Map<String, String>)     theCaseInsensitiveEnvironmentField.get(null);
    cienv.putAll(newenv);
  } catch (NoSuchFieldException e) {
    Class[] classes = Collections.class.getDeclaredClasses();
    Map<String, String> env = System.getenv();
    for(Class cl : classes) {
      if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
        Field field = cl.getDeclaredField("m");
        field.setAccessible(true);
        Object obj = field.get(env);
        Map<String, String> map = (Map<String, String>) obj;
        map.clear();
        map.putAll(newenv);
      }
    }
  }
}

这就像一个魅力。这些黑客的两位作者的完全信用。


#2 热门回答(75 赞)

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

我觉得你已经敲了敲头。

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

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

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

此外,你可能已经知道这一点,但你可以使用相同的ProcessBuilder启动多个进程。因此,如果你的子进程相同,则无需反复进行此设置。


#3 热门回答(42 赞)

public static void set(Map<String, String> newenv) throws Exception {
    Class[] classes = Collections.class.getDeclaredClasses();
    Map<String, String> env = System.getenv();
    for(Class cl : classes) {
        if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
            Field field = cl.getDeclaredField("m");
            field.setAccessible(true);
            Object obj = field.get(env);
            Map<String, String> map = (Map<String, String>) obj;
            map.clear();
            map.putAll(newenv);
        }
    }
}