如何从Java设置环境变量?我看到我可以使用子进程执行此操作ProcessBuilder
.我有几个子进程要启动,所以我宁愿修改当前进程的环境,让子进程继承它.
有一个System.getenv(String)
获取单个环境变量.我也可以获得一套Map
完整的环境变量System.getenv()
.但是,要求put()
对Map
引发UnsupportedOperationException
-显然,他们的意思是对环境只能被读取.并且,没有System.setenv()
.
那么,有没有办法在当前运行的进程中设置环境变量?如果是这样,怎么样?如果没有,理由是什么?(这是因为这是Java,因此我不应该做一些邪恶的非便携式过时的事情,比如触摸我的环境吗?)如果没有,那么管理环境变量的任何好建议都会改变我需要提供给几个子进程?
要在需要为单元测试设置特定环境值的场景中使用,您可能会发现以下hack非常有用.它将更改整个JVM中的环境变量(因此请确保在测试后重置所有更改),但不会改变您的系统环境.
我发现爱德华·坎贝尔和匿名的两个脏黑客的组合效果最好,因为其中一个在linux下不起作用,一个在Windows 7下不起作用.所以为了得到一个多平台的邪恶黑客,我把它们组合在一起:
protected static void setEnv(Mapnewenv) throws Exception { try { Class> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment"); theEnvironmentField.setAccessible(true); Map env = (Map ) theEnvironmentField.get(null); env.putAll(newenv); Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment"); theCaseInsensitiveEnvironmentField.setAccessible(true); Map cienv = (Map ) theCaseInsensitiveEnvironmentField.get(null); cienv.putAll(newenv); } catch (NoSuchFieldException e) { Class[] classes = Collections.class.getDeclaredClasses(); Map 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 map = (Map ) obj; map.clear(); map.putAll(newenv); } } } }
这个作品就像一个魅力.对这些黑客的两位作者的完全信任.
(这是因为这是Java,因此我不应该做一些邪恶的非便携式过时的东西,比如触摸我的环境吗?)
我觉得你已经敲了敲头.
减轻负担的一种可能方法是分解出一种方法
void setUpEnvironment(ProcessBuilder builder) { Mapenv = builder.environment(); // blah blah }
并ProcessBuilder
在启动之前传递任何s.
此外,您可能已经知道这一点,但您可以使用相同的方法启动多个进程ProcessBuilder
.因此,如果您的子流程相同,则无需反复进行此设置.
public static void set(Mapnewenv) throws Exception { Class[] classes = Collections.class.getDeclaredClasses(); Map 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 map = (Map ) obj; map.clear(); map.putAll(newenv); } } }
// this is a dirty hack - but should be ok for a unittest. private void setNewEnvironmentHack(Mapnewenv) throws Exception { Class> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment"); theEnvironmentField.setAccessible(true); Map env = (Map ) theEnvironmentField.get(null); env.clear(); env.putAll(newenv); Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment"); theCaseInsensitiveEnvironmentField.setAccessible(true); Map cienv = (Map ) theCaseInsensitiveEnvironmentField.get(null); cienv.clear(); cienv.putAll(newenv); }
在Android上,该接口通过Libcore.os作为一种隐藏的API公开.
Libcore.os.setenv("VAR", "value", bOverwrite); Libcore.os.getenv("VAR"));
Libcore类以及接口操作系统是公共的.只缺少类声明,需要向链接器显示.无需将类添加到应用程序中,但如果包含它也不会受到影响.
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; }
设置单个环境变量(基于Edward Campbell的回答):
public static void setEnv(String key, String value) { try { Mapenv = System.getenv(); Class> cl = env.getClass(); Field field = cl.getDeclaredField("m"); field.setAccessible(true); Map writableEnv = (Map ) 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"
回来的.
事实证明来自@ pushy/@ anonymous/@ Edward Campbell的解决方案在Android上不起作用,因为Android不是真正的Java.具体来说,Android根本没有java.lang.ProcessEnvironment
.但事实证明在Android中更容易,你只需要对POSIX进行JNI调用setenv()
:
在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);
}
}
这是@ paul-blair转换为Java的答案的组合,其中包括由保罗布莱尔指出的一些清理以及似乎在@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 staticvoid 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 env = (Map ) 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 variableClass = (Class ) 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 valueClass = (Class ) 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 cienv = (Map ) 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 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 map = (Map ) 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)); }