How do I set environment variables from Java?
如何从Java设置环境变量? 我看到我可以对使用
有一个
那么,有什么方法可以在当前运行的进程中设置环境变量? 如果是这样,怎么办? 如果没有,有什么根据? (是因为这是Java,所以我不应该像触摸我的环境那样做邪恶的,不可移植的过时的事情吗?)如果没有,关于管理环境变量更改的任何好的建议将需要反馈给几个子进程?
对于需要为单元测试设置特定环境值的方案,您可能会发现以下技巧很有用。它会在整个JVM中更改环境变量(因此请确保在测试后重置所有更改),但不会更改系统环境。
我发现爱德华·坎贝尔和匿名的两个肮脏黑客的组合效果最好,因为其中一个在Linux下不起作用,一个在Windows 7下不起作用。因此,为了获得多平台邪恶的黑客,我将它们结合在一起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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); } } } } |
这就像一个魅力。完全归功于这些骇客的两位作者。
(Is it because this is Java and therefore I shouldn't be doing evil nonportable obsolete things like touching my environment?)
我认为您已经打中了头。
减轻负担的一种可能方法是排除一种方法
1 2 3 4 | void setUpEnvironment(ProcessBuilder builder) { Map<String, String> env = builder.environment(); // blah blah } |
并在启动它们之前将所有
另外,您可能已经知道这一点,但是可以使用相同的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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并删除循环。
1 2 3 4 5 6 7 | @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); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 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); } |
在Android上,该接口通过Libcore.os作为一种隐藏的API公开。
1 2 | Libcore.os.setenv("VAR","value", bOverwrite); Libcore.os.getenv("VAR")); |
Libcore类以及接口OS是公共的。仅缺少类声明,需要将其显示给链接器。无需将类添加到应用程序中,但是如果包含了类,也不会受到损害。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
仅Linux
设置单个环境变量(基于Edward Campbell的回答):
1 2 3 4 5 6 7 8 9 10 11 12 | 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。
1 | SystemUtil.setEnv("SHELL","/bin/bash"); |
如果在此之后调用
事实证明,@ pushy / @ anonymous / @ Edward Campbell的解决方案在Android上不起作用,因为Android并不是真正的Java。具体来说,Android根本没有
在C / JNI中:
1 2 3 4 5 6 7 8 9 10 | 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中:
1 2 3 4 5 6 7 8 |
这是@ paul-blair的答案转换为Java的组合,其中包括paul blair指出的一些清除以及似乎在@pushy的代码中的一些错误,这些错误由@Edward Campbell和匿名用户组成。
我无法强调此代码仅应在测试中使用多少,并且非常hacky。但是对于需要在测试中进行环境设置的情况,这正是我所需要的。
这还包括我的一些小改动,使代码可以在运行Windows的两个Windows上运行。
1 2 3 | 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正在运行
1 2 3 | 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) |
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | /** * 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)); } |
像大多数发现此线程的人一样,我正在编写一些单元测试,需要修改环境变量以设置正确的条件以运行测试。但是,我发现最受欢迎的答案存在一些问题,并且/或者非常含糊或过于复杂。希望这将帮助其他人更快地解决问题。
首先,我终于发现@Hubert Grzeskowiak的解决方案是最简单的,并且对我有用。我希望我先来谈那个。它基于@Edward Campbell的答案,但没有使循环搜索复杂化。
但是,我从@pushy的解决方案开始,该解决方案获得最多的支持。它是@anonymous和@Edward Campbell的组合。 @pushy声称,这两种方法都需要涵盖Linux和Windows环境。我在OS X上运行,发现两者都可以工作(一旦解决了@anonymous方法的问题)。正如其他人指出的那样,此解决方案在大多数情况下有效,但并非全部。
我认为大多数混淆的根源来自在'theEnvironment'字段上运行的@anonymous解决方案。查看ProcessEnvironment结构的定义," theEnvironment"不是Map
就我而言,我需要为测试删除一些环境变量(其他变量应保持不变)。然后,我想在测试后将环境变量恢复到先前的状态。下面的例程可以帮助您做到这一点。我在OS X上测试了两个版本的getModifiableEnvironmentMap,并且两个版本均等效。尽管基于该线程中的注释,但根据环境,一个可能比另一个更好。
注意:我没有包括对" theCaseInsensitiveEnvironmentField"的访问,因为这似乎是Windows特定的,并且我无法对其进行测试,但是添加起来应该很简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | 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); } |
在上面尝试了pushy的答案,并且在大多数情况下都有效。但是,在某些情况下,我会看到此异常:
1 |
由于实施某些
下面是一个固定版本(在Scala中)。希望可以将它移植到Java中不太困难。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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() } } |
在网上闲逛,似乎可以使用JNI做到这一点。然后,您必须从C调用putenv(),并且(大概)必须在Windows和UNIX上都可以使用。
如果所有这些都能做到,那么Java本身就可以支持它,而不是直接穿上外套,肯定不会太难。
在其他地方讲Perl的朋友认为,这是因为环境变量是全局过程的,而Java则在努力为好的设计提供良好的隔离。
如果使用SpringBoot,则可以在以下属性中添加指定环境变量:
1 | was.app.config.properties.toSystemProperties |
这是@pushy的邪恶答案的Kotlin邪恶版本=)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | @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中可以运行。
我最近根据爱德华的答案进行了Kotlin的实现:
1 2 3 4 5 6 7 8 9 10 11 | 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) } } |
您可以使用-D将参数传递到初始的Java进程中:
1 | java -cp <classpath> -Dkey1=value -Dkey2=value ... |