题
我怎么设置的环境变量从Java?我看到,我能做这个对子流程使用 ProcessBuilder
.我有几个子过程的开始,虽然如此我还是修改当前进程的环境和让子流程继承。
还有一个 System.getenv(String)
得到一个单一环境变量。我也可以得到一个 Map
完整套环境变量 System.getenv()
.但是,叫 put()
在那 Map
会引发一个 UnsupportedOperationException
--显然,他们意味着对环境以只读。而且,有的没有 System.setenv()
.
那么,是否有任何方式集中的环境变量,当前正在运行过程?如果是这样,怎么样?如果不是,有什么理由?(这是因为这是Java因此,我不应该这样做邪恶的非过时的东西喜欢抚摸我的环境?) 如果没有,什么好的建议,用于管理环境变量的变化,我需要喂养几个子过程?
解决方案
(难道是因为这是Java的,所以我不应该做坏事不可移植的过时的东西喜欢摸我的环境?)
我想你已经击中了要害。
要减轻负担的一种可能的方法是将分解出的方法
void setUpEnvironment(ProcessBuilder builder) {
Map<String, String> env = builder.environment();
// blah blah
}
和开始之前通过它的任何ProcessBuilder
s。
此外,你可能已经知道这一点,但你可以用相同的ProcessBuilder
启动多个进程。所以,如果你的子过程都是一样的,你不需要一遍又一遍地做此设置。
其他提示
有关在您需要的单元测试设置特定的环境值的情况使用,你可能会发现下面的技巧很有用。它会改变的环境变量在整个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);
}
}
}
}
这就像一个魅力。充分信用这些黑客的两位作者。
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);
}
}
}
或添加/更新单个var和去除所述循环按thejoshwolfe的建议。
@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);
}
// 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。
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;
}
仅Linux
设置单个环境变量(由Edward坎贝尔基于回答):
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"
回来。
原来,从@解爱出风头/ @匿名/ @爱德华·坎贝尔并没有在Android上工作,因为Android是不是真正的Java。具体而言,Android不都具有java.lang.ProcessEnvironment
。但事实证明,要在Android的更容易,你只需要做一个JNI调用POSIX 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布莱尔指出并且一些错误,似乎已经内部@pushy的答案转换为Java”,其是由@Edward Campbell和第代码@保布莱尔的组合匿名。
我不能强调多少,这代码只应在测试中使用,是非常哈克。但是,在您需要的环境设置的测试情况下,正是我需要的。
此也包括矿的一些小的接触,允许代码在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));
}
闲逛网络,它看起来像有可能使用JNI来做到这一点。然后,你就必须拨打电话从C putenv()函数,和你(大概)必须做的方式,在Windows和UNIX工作。
如果一切可以做到的,它肯定不会是太难为Java本身支持这不是把我在一条直线夹克。
一个Perl的朋友说别处表明,这是因为环境变量是全球流程和Java正在努力为优秀的设计很好的隔离。
试过爱出风头的回答上面,它的工作大部分。然而,在某些情况下,我会看到这个异常:
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()
}
}
像大多数人已经发现这个线程,我编写一些单元测试和所需修改的环境变量设定正确的条件试验运行。然而,我发现的大多数投赞成票的答案有一些问题和/或非常含糊或过于复杂。希望这将有助于其他的解决方案更加迅速。
首先,我终于找到@Hubert Grzeskowiak的解决方案是最简单和它为我工作。我希望我会来到这个第一。这是基于@爱德华*坎贝尔的答复,但没有复杂的循环搜索。
但是,我开始与@爱出风头的解决方案,它得到了最多的"顶".它是一种组合的@匿名和@爱德华*坎贝尔。@爱出风头的索赔这两种方法都需要以复盖Linux和Windows环境。我下运行OS X和找到工作(一旦一个问题@匿名的方法是固定的)。如其他人已经指出,这种解决方案的工作大部分时间,但不是全部。
我认为来源的大部分混乱来自@匿名的解决方案的操作上的'环境'的领域。看的定义 参考 结构、'环境'不是一个地图< String,String>而是它是一个地图< 变量值>。清除地图工作的现,但putAll作重新生成地的地图< String,String>,这可能导致的问题,当后续行动操作数据结构的使用正常的API,预计地图< 变量值>。此外,访问/删除的各个元素是一个问题。该方案是来访问的'环境'间接地通过'theUnmodifiableEnvironment'.但是,由于这是一种类型 UnmodifiableMap 访问必须通过私变量'm'的UnmodifiableMap类型。见getModifiableEnvironmentMap2在代码如下。
在我的情况下,我需要消除某些环境变量对我的测试(其他人应当不变)。然后我想到恢复环境的变量,为它们的前后的状态测试。例行程序下做出的,直接向前做。我测试了两个版本的getModifiableEnvironmentMap在OS X和两个工作效地.虽然根据的评论在这个线程,一个可能是更好的选择比其他具体取决于环境。
注:我不包括访问'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);
}
这是在@冲劲的恶回答 =)
@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莫哈韦至少工作。
科特林实现我最近根据爱德华的回答让:
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)
}
}
可以将参数传递到您的初始java程序用-D:
java -cp <classpath> -Dkey1=value -Dkey2=value ...