ChatGPT解决这个技术问题 Extra ChatGPT

如何从 Java 设置环境变量?

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

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

那么,有没有办法在当前运行的进程中设置环境变量呢?如果是这样,怎么做?如果不是,原因是什么? (是不是因为这是 Java,所以我不应该做邪恶的不可移植的过时的事情,比如触摸我的环境?)如果不是,任何关于管理环境变量更改的好建议,我需要提供给几个子进程?

System.getEnv() 旨在通用,某些环境甚至没有环境变量。
对于需要此单元测试用例的任何人:stackoverflow.com/questions/8168884/…

m
marcolopes

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

我发现 Edward Campbell 和anonymous 的两个肮脏黑客的组合效果最好,因为一个在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);
      }
    }
  }
}

这就像一个魅力。完全归功于这些黑客的两位作者。


这只会改变内存,还是实际上改变系统中的整个环境变量?
这只会改变内存中的环境变量。这有利于测试,因为您可以根据需要设置测试所需的环境变量,但将环境保留在系统中。事实上,我强烈反对任何人将此代码用于测试以外的任何其他目的。这段代码是邪恶的;-)
作为仅供参考,JVM 在启动时会创建环境变量的副本。这将编辑该副本,而不是启动 JVM 的父进程的环境变量。
我在Android上试过这个,它似乎没有。其他人在Android上有运气吗?
当然,import java.lang.reflect.Field;
M
Michael Myers

(是不是因为这是 Java,所以我不应该做邪恶的不可移植的过时的事情,比如触摸我的环境?)

我想你已经一针见血了。

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

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

并在启动它们之前通过它传递任何 ProcessBuilder

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


遗憾的是,管理层不允许我使用不同的可移植语言来运行这组邪恶的、过时的子进程。 :)
S.Lott,我不想设置父母的环境。我正在寻找设置自己的环境。
这很好用,除非是其他人的库(例如 Sun 的)正在启动该过程。
@b1naryatr0phy 你错过了重点。没有人可以使用您的环境变量,因为这些变量是进程的本地变量(您在 Windows 中设置的是默认值)。每个进程都可以自由更改自己的变量……除非是 Java。
java的这种限制有点像警察。除了“因为我们不希望 java 这样做”之外,java 没有理由不让你设置环境变量。
K
Kashyap
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);
        }
    }
}

或者根据 thejoshwolfe 的建议添加/更新单个 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);
  }

听起来这会修改内存中的映射,但它会将值保存到系统中吗?
它确实改变了环境变量的内存映射。我想这在很多用例中就足够了。 @Edward - 天哪,很难想象这个解决方案是如何首先想到的!
这不会改变系统上的环境变量,但会在当前的 Java 调用中改变它们。这对于单元测试非常有用。
为什么不使用 Class<?> cl = env.getClass(); 而不是 for 循环?
这正是我一直在寻找的!我一直在为一些使用第三方工具的代码编写集成测试,出于某种原因,该工具只允许您使用环境变量修改其荒谬的短默认超时长度。
H
Hubert Grzeskowiak

设置单个环境变量(基于 Edward Campbell 的回答):

public static void setEnv(String key, String value) {
    try {
        Map<String, String> env = System.getenv();
        Class<?> cl = env.getClass();
        Field field = cl.getDeclaredField("m");
        field.setAccessible(true);
        Map<String, String> writableEnv = (Map<String, String>) field.get(env);
        writableEnv.put(key, value);
    } catch (Exception e) {
        throw new IllegalStateException("Failed to set environment variable", e);
    }
}

用法:

首先,将该方法放在您想要的任何类中,例如 SystemUtil。然后静态调用它:

SystemUtil.setEnv("SHELL", "/bin/bash");

如果您在此之后调用 System.getenv("SHELL"),您将得到 "/bin/bash"


以上不适用于Windows 10,但可以在linux中使用。
有趣的。我自己没有在 Windows 上尝试过。 @mengchengfeng 你有错误吗?
@HubertGrzeskowiak 我们没有看到任何错误消息,它只是没有用......
它适用于我的 Windows 10 和 linux。恭喜!
为什么这只是 Linux/Mac?显然 Bash 可能默认不存在,但这里的 Java 代码不依赖于平台。
Y
Yan Foto
// this is a dirty hack - but should be ok for a unittest.
private void setNewEnvironmentHack(Map<String, String> newenv) throws Exception
{
  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.clear();
  env.putAll(newenv);
  Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
  theCaseInsensitiveEnvironmentField.setAccessible(true);
  Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
  cienv.clear();
  cienv.putAll(newenv);
}

u
user3404318

在 Android 上,接口通过 Libcore.os 作为一种隐藏的 API 公开。

Libcore.os.setenv("VAR", "value", bOverwrite);
Libcore.os.getenv("VAR"));

Libcore 类以及接口 OS 是公共的。只是缺少类声明,需要向链接器显示。无需将类添加到应用程序中,但如果包含它也没有什么坏处。

package libcore.io;

public final class Libcore {
    private Libcore() { }

    public static Os os;
}

package libcore.io;

public interface Os {
    public String getenv(String name);
    public void setenv(String name, String value, boolean overwrite) throws ErrnoException;
}

在 Android 4.4.4 (CM11) 上测试并运行。 PS 我所做的唯一调整是将 throws ErrnoException 替换为 throws Exception
API 21,现在有 Os.setEnvdeveloper.android.com/reference/android/system/…,java.lang.String,布尔值)
现在可能因 Pie 的新限制而失效:developer.android.com/about/versions/pie/…
m
mangusbrother

这是@paul-blair 的答案转换为Java 的组合,其中包括paul blair 指出的一些清理和一些似乎存在于@pushy 的代码中的错误,该代码由@Edward Campbell 和匿名组成。

我无法强调这段代码应该只在测试中使用多少,而且非常hacky。但是对于需要在测试中设置环境的情况,这正是我所需要的。

这还包括我的一些小改动,允许代码在两个 Windows 上运行

java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)

以及运行的 Centos

openjdk version "1.8.0_91"
OpenJDK Runtime Environment (build 1.8.0_91-b14)
OpenJDK 64-Bit Server VM (build 25.91-b14, mixed mode)

实施:

/**
 * Sets an environment variable FOR THE CURRENT RUN OF THE JVM
 * Does not actually modify the system's environment variables,
 *  but rather only the copy of the variables that java has taken,
 *  and hence should only be used for testing purposes!
 * @param key The Name of the variable to set
 * @param value The value of the variable to set
 */
@SuppressWarnings("unchecked")
public static <K,V> void setenv(final String key, final String value) {
    try {
        /// we obtain the actual environment
        final Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
        final Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
        final boolean environmentAccessibility = theEnvironmentField.isAccessible();
        theEnvironmentField.setAccessible(true);

        final Map<K,V> env = (Map<K, V>) theEnvironmentField.get(null);

        if (SystemUtils.IS_OS_WINDOWS) {
            // This is all that is needed on windows running java jdk 1.8.0_92
            if (value == null) {
                env.remove(key);
            } else {
                env.put((K) key, (V) value);
            }
        } else {
            // This is triggered to work on openjdk 1.8.0_91
            // The ProcessEnvironment$Variable is the key of the map
            final Class<K> variableClass = (Class<K>) Class.forName("java.lang.ProcessEnvironment$Variable");
            final Method convertToVariable = variableClass.getMethod("valueOf", String.class);
            final boolean conversionVariableAccessibility = convertToVariable.isAccessible();
            convertToVariable.setAccessible(true);

            // The ProcessEnvironment$Value is the value fo the map
            final Class<V> valueClass = (Class<V>) Class.forName("java.lang.ProcessEnvironment$Value");
            final Method convertToValue = valueClass.getMethod("valueOf", String.class);
            final boolean conversionValueAccessibility = convertToValue.isAccessible();
            convertToValue.setAccessible(true);

            if (value == null) {
                env.remove(convertToVariable.invoke(null, key));
            } else {
                // we place the new value inside the map after conversion so as to
                // avoid class cast exceptions when rerunning this code
                env.put((K) convertToVariable.invoke(null, key), (V) convertToValue.invoke(null, value));

                // reset accessibility to what they were
                convertToValue.setAccessible(conversionValueAccessibility);
                convertToVariable.setAccessible(conversionVariableAccessibility);
            }
        }
        // reset environment accessibility
        theEnvironmentField.setAccessible(environmentAccessibility);

        // we apply the same to the case insensitive environment
        final Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
        final boolean insensitiveAccessibility = theCaseInsensitiveEnvironmentField.isAccessible();
        theCaseInsensitiveEnvironmentField.setAccessible(true);
        // Not entirely sure if this needs to be casted to ProcessEnvironment$Variable and $Value as well
        final Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
        if (value == null) {
            // remove if null
            cienv.remove(key);
        } else {
            cienv.put(key, value);
        }
        theCaseInsensitiveEnvironmentField.setAccessible(insensitiveAccessibility);
    } catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+">", e);
    } catch (final NoSuchFieldException e) {
        // we could not find theEnvironment
        final Map<String, String> env = System.getenv();
        Stream.of(Collections.class.getDeclaredClasses())
                // obtain the declared classes of type $UnmodifiableMap
                .filter(c1 -> "java.util.Collections$UnmodifiableMap".equals(c1.getName()))
                .map(c1 -> {
                    try {
                        return c1.getDeclaredField("m");
                    } catch (final NoSuchFieldException e1) {
                        throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+"> when locating in-class memory map of environment", e1);
                    }
                })
                .forEach(field -> {
                    try {
                        final boolean fieldAccessibility = field.isAccessible();
                        field.setAccessible(true);
                        // we obtain the environment
                        final Map<String, String> map = (Map<String, String>) field.get(env);
                        if (value == null) {
                            // remove if null
                            map.remove(key);
                        } else {
                            map.put(key, value);
                        }
                        // reset accessibility
                        field.setAccessible(fieldAccessibility);
                    } catch (final ConcurrentModificationException e1) {
                        // This may happen if we keep backups of the environment before calling this method
                        // as the map that we kept as a backup may be picked up inside this block.
                        // So we simply skip this attempt and continue adjusting the other maps
                        // To avoid this one should always keep individual keys/value backups not the entire map
                        LOGGER.info("Attempted to modify source map: "+field.getDeclaringClass()+"#"+field.getName(), e1);
                    } catch (final IllegalAccessException e1) {
                        throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+">. Unable to access field!", e1);
                    }
                });
    }
    LOGGER.info("Set environment variable <"+key+"> to <"+value+">. Sanity Check: "+System.getenv(key));
}

它有效,像这样使用:setenv("coba","coba value"); System.out.println(System.getenv("coba")); 结果:Set environment variable <coba> to <coba value>. Sanity Check: coba value coba value
H
Hans-Christoph Steiner

事实证明,@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);
    }
}

T
Tim Ryan

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

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

但是,我从@pushy 的解决方案开始,它得到了最多的支持。它是@anonymous 和@Edward Campbell 的组合。 @pushy 声称这两种方法都需要涵盖 Linux 和 Windows 环境。我在 OS X 下运行,发现两者都可以工作(一旦解决了@anonymous 方法的问题)。正如其他人所指出的,此解决方案在大多数情况下都有效,但并非全部。

我认为大多数混乱的根源来自@anonymous 在“环境”字段上运行的解决方案。查看 ProcessEnvironment 结构的定义,'theEnvironment' 不是 Map<;字符串,字符串>而是它是一个地图<变量,值 >。清除地图可以正常工作,但 putAll 操作会重建地图 a Map<; String, String >,当后续操作使用期望 Map< 的普通 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);
}

谢谢,这正是我的用例,也是在 mac os x 下。
非常喜欢这个,我为 Groovy 制作了一个稍微简单的版本,见下文。
P
Paul Blair

在上面尝试了 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()
  }
}

JavaClass 在哪里定义?
大概是import java.lang.{Class => JavaClass}
即使对于相同的构建,java.lang.ProcessEnvironment 在不同平台上的实现也是不同的。例如,在 Windows 的实现中没有 java.lang.ProcessEnvironment$Variable 类,但在 Linux 中这个类存在。你可以很容易地检查它。只需下载适用于 Linux 的 tar.gz JDK 发行版并从 src.zip 中提取源代码,然后将其与适用于 Windows 的发行版中的相同文件进行比较。它们在 JDK 1.8.0_181 中完全不同。我没有在 Java 10 中检查过它们,但如果有相同的图片,我不会感到惊讶。
s
skiphoppy

在网上四处寻找,似乎可以使用 JNI 来做到这一点。然后,您必须从 C 中调用 putenv(),并且(可能)必须以在 Windows 和 UNIX 上都有效的方式来执行此操作。

如果所有这些都可以完成,那么 Java 本身支持它肯定不会太难,而不是让我穿上一件直截了当的夹克。

一位在其他地方讲 Perl 的朋友建议这是因为环境变量是进程全局的,而 Java 正在努力实现良好的隔离以实现良好的设计。


是的,您可以从 C 代码设置流程环境。但我不会指望在 Java 中工作。 JVM 很有可能在启动期间将环境复制到 Java String 对象中,因此您的更改不会用于未来的 JVM 操作。
谢谢你的警告,达伦。很有可能你是对的。
@Darron 想要这样做的许多原因与 JVM 认为的环境完全无关。 (考虑在调用 Runtime.loadLibrary() 之前设置 LD_LIBRARY_PATH;它调用的 dlopen() 调用查看的是 real 环境,而不是 Java 的相同想法)。
这适用于由本机库启动的子进程(在我的情况下是其中的大多数),但不幸的是不适用于由 Java 的 Process 或 ProcessBuilder 类启动的子进程。
A
Ashley Frieze

有三个库可以在单元测试期间执行此操作。

Stefan Birkner 的系统规则和系统 Lambda - https://www.baeldung.com/java-system-rules-junit 允许您执行以下操作:

public class JUnitTest {

    @Rule
    public EnvironmentVariables environmentVariables = new EnvironmentVariables();

    @Test
    public void someTest() {
        environmentVariables.set("SOME_VARIABLE", "myValue");
        
        // now System.getenv does what you want
    }
}

或对于 System-Lambda:

@Test
void execute_code_with_environment_variables(
) throws Exception {
  List<String> values = withEnvironmentVariable("first", "first value")
    .and("second", "second value")
    .execute(() -> asList(
      System.getenv("first"),
      System.getenv("second")
    ));
  assertEquals(
    asList("first value", "second value"),
    values
  );
}

上述功能也可通过系统存根作为 JUnit 5 扩展使用:

@ExtendWith(SystemStubsExtension.class)
class SomeTest {

    @SystemStub
    private EnvironmentVariables;

    @Test
    void theTest() {
        environmentVariables.set("SOME_VARIABLE", "myValue");
        
        // now System.getenv does what you want

    }

}

System Stubs 向后兼容 System Lambda 和 System Rules,但支持 JUnit 5。

或者,还有 JUnit Pioneer - https://github.com/junit-pioneer/junit-pioneer,它允许在测试时通过注释设置环境变量。


JUnit Pioneer 非常出色。关于测试方法的简单 @SetEnvironmentVariable(key = "SOME_VARIABLE", value = "myValue"),您就可以开始了。
是的,但是在编码时必须知道环境变量值,这对于设置动态端口等内容不起作用。
m
mike rodent

Tim Ryan 的回答对我有用......但我想要它用于 Groovy(例如 Spock 上下文)和 simplissimo:

import java.lang.reflect.Field

def getModifiableEnvironmentMap() {
    def unmodifiableEnv = System.getenv()
    Class cl = unmodifiableEnv.getClass()
    Field field = cl.getDeclaredField("m")
    field.accessible = true
    field.get(unmodifiableEnv)
}

def clearEnvironmentVars( def keys ) {
    def savedVals = [:]
    keys.each{ key ->
        String val = modifiableEnvironmentMap.remove(key)
        // thinking about it, I'm not sure why we need this test for null
        // but haven't yet done any experiments
        if( val != null ) {
            savedVals.put( key, val )
        }
    }
    savedVals
}

def setEnvironmentVars(Map varMap) {
    modifiableEnvironmentMap.putAll(varMap)
}

// pretend existing Env Var doesn't exist
def PATHVal1 = System.env.PATH
println "PATH val1 |$PATHVal1|"
String[] keys = ["PATH", "key2", "key3"]
def savedVars = clearEnvironmentVars(keys)
def PATHVal2 = System.env.PATH
println "PATH val2 |$PATHVal2|"

// return to reality
setEnvironmentVars(savedVars)
def PATHVal3 = System.env.PATH
println "PATH val3 |$PATHVal3|"
println "System.env |$System.env|"

// pretend a non-existent Env Var exists
setEnvironmentVars( [ 'key4' : 'key4Val' ])
println "key4 val |$System.env.key4|"

J
Johan Ansems

在使用依赖于相关环境变量的本机代码(dll)的当前 Java 进程中设置环境变量,仅当您本机设置此环境变量时才有效。

此处的大多数示例都在更改 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接口,但没有尝试过。


if( isMac() || isLinux() ) { if( LibC.INSTANCE.setenv( key, value, 1 ) != 0 ) { System.err.println( "Unable to set the environemnt variable: " + key ); } } 在 linux 和 macos 上有效。
d
deddu

如果您像我一样在测试中遇到这个问题,并且您正在使用 Junit5,那么 Junit-pioneer 会附带非常有用的注释。 maven release

例子:

@Test
@SetEnvironmentVariable(key = "some variable",value = "new value")
void test() {
    assertThat(System.getenv("some variable")).
        isEqualTo("new value");
}

不能推荐它。


G
GarouDan

这是@pushy 的邪恶 answer =) 的 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 中运行。


A
Arun Sharma

我偶然发现了这个线程,因为我有一个类似的要求,我需要(永久)设置(或更新)环境变量。

所以我查看了 - 如何从命令提示符永久设置环境变量,这非常简单!

setx JAVA_LOC C:/Java/JDK

然后我只是在我的代码中实现了相同的这是我使用的(假设 - JAVA_LOC 是环境变量名)

        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/程序的生命周期无关。


g
geosmart

只需使用反射,例如 setEnv("k1","v1")

    private void setEnv(String key, String val) throws Exception {
        getModifiableEnv().put(key, val);
    }
    
    private Map<String, String> getModifiableEnv() throws Exception {
        Map<String, String> unmodifiableEnv = System.getenv();
        Field field = unmodifiableEnv.getClass().getDeclaredField("m");
        field.setAccessible(true);
        return (Map<String, String>) field.get(unmodifiableEnv);
    }

需要

import java.lang.reflect.Field;
import java.util.Map;

m
marc_s

基于 @pushy's 答案的变体,适用于 Windows。

def set_env(newenv):
    from java.lang import Class
    process_environment = Class.forName("java.lang.ProcessEnvironment")
    environment_field =  process_environment.getDeclaredField("theEnvironment")
    environment_field.setAccessible(True)
    env = environment_field.get(None)
    env.putAll(newenv)
    invariant_environment_field = process_environment.getDeclaredField("theCaseInsensitiveEnvironment");
    invariant_environment_field.setAccessible(True)
    invevn = invariant_environment_field.get(None)
    invevn.putAll(newenv)

用法:

old_environ = dict(os.environ)
old_environ['EPM_ORACLE_HOME'] = r"E:\Oracle\Middleware\EPMSystem11R1"
set_env(old_environ)

T
Tiarê Balbi

Kotlin 中的一个版本,在这个算法中,我创建了一个装饰器,允许您从环境中设置和获取变量。

import java.util.Collections
import kotlin.reflect.KProperty
​
class EnvironmentDelegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return System.getenv(property.name) ?: "-"
    }
​
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        val key = property.name
​
        val classes: Array<Class<*>> = Collections::class.java.declaredClasses
        val env = System.getenv()
​
        val cl = classes.first { "java.util.Collections\$UnmodifiableMap" == it.name }
​
        val field = cl.getDeclaredField("m")
        field.isAccessible = true
        val obj = field[env]
        val map = obj as MutableMap<String, String>
        map.putAll(mapOf(key to value))
    }
}
​
class KnownProperties {
    var JAVA_HOME: String by EnvironmentDelegate()
    var sample: String by EnvironmentDelegate()
}
​
fun main() {
    val knowProps = KnownProperties()
    knowProps.sample = "2"
​
    println("Java Home: ${knowProps.JAVA_HOME}")
    println("Sample: ${knowProps.sample}")
}

R
Rik

我最近根据 Edward 的回答制作的 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)
    }
}

A
Alex

如果您使用 SpringBoot,您可以在以下属性中添加指定环境变量:

was.app.config.properties.toSystemProperties

你能解释一下吗?
系统属性与环境变量不同。
m
matt b

您可以使用 -D 将参数传递到初始 java 进程中:

java -cp <classpath> -Dkey1=value -Dkey2=value ...

这些值在执行时是未知的;当用户提供/选择它们时,它们在程序执行期间变得已知。并且只设置系统属性,而不是环境变量。
然后在这种情况下,您可能希望找到一种常规方式(通过主方法的 args[] 参数)来调用您的子流程。
马特 b,常规方式是通过 ProcessBuilder,如我原来的问题中所述。 :)
-D 参数可通过 System.getProperty 获得,并且与 System.getenv 不同。此外,System 类还允许使用 setProperty 静态设置这些属性