如何从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接口,但还没有尝试过。

其他回答

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);
        }
    }
}

或者根据joshwolfe的建议添加/更新一个单独的var并删除循环。

@SuppressWarnings({ "unchecked" })
  public static void updateEnv(String name, String val) throws ReflectiveOperationException {
    Map<String, String> env = System.getenv();
    Field field = env.getClass().getDeclaredField("m");
    field.setAccessible(true);
    ((Map<String, String>) field.get(env)).put(name, val);
  }

在当前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接口,但还没有尝试过。

事实证明,@pushy/@anonymous/@Edward Campbell的解决方案在Android上不起作用,因为Android并不是真正的Java。具体来说,Android根本没有java.lang.ProcessEnvironment。但事实证明在Android中更容易,你只需要对POSIX setenv()进行JNI调用:

打印C / JNI:

JNIEXPORT jint JNICALL Java_com_example_posixtest_Posix_setenv
  (JNIEnv* env, jclass clazz, jstring key, jstring value, jboolean overwrite)
{
    char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL);
    char* v = (char *) (*env)->GetStringUTFChars(env, value, NULL);
    int err = setenv(k, v, overwrite);
    (*env)->ReleaseStringUTFChars(env, key, k);
    (*env)->ReleaseStringUTFChars(env, value, v);
    return err;
}

在Java中:

public class Posix {

    public static native int setenv(String key, String value, boolean overwrite);

    private void runTest() {
        Posix.setenv("LD_LIBRARY_PATH", "foo", true);
    }
}

Kotlin实现我最近基于Edward的回答:

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)
    }
}

这是@pushy的邪恶答案的Kotlin邪恶版本=)

@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.getDeclaredClasses()
        val env = System.getenv()
        for (cl in classes) {
            if ("java.util.Collections\$UnmodifiableMap" == cl.getName()) {
                val field = cl.getDeclaredField("m")
                field.setAccessible(true)
                val obj = field.get(env)
                val map = obj as MutableMap<String, String>
                map.clear()
                map.putAll(newenv)
            }
        }
    }

它至少在macOS Mojave上运行。