如何从 Java 设置环境变量?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/318239/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I set environment variables from Java?
提问by skiphoppy
How do I set environment variables from Java? I see that I can do this for subprocesses using ProcessBuilder
. I have several subprocesses to start, though, so I'd rather modify the current process's environment and let the subprocesses inherit it.
如何从 Java 设置环境变量?我看到我可以使用ProcessBuilder
. 不过,我有几个子进程要启动,所以我宁愿修改当前进程的环境并让子进程继承它。
There's a System.getenv(String)
for getting a single environment variable. I can also get a Map
of the complete set of environment variables with System.getenv()
. But, calling put()
on that Map
throws an UnsupportedOperationException
-- apparently they mean for the environment to be read only. And, there's no System.setenv()
.
有一个System.getenv(String)
用于获取单个环境变量。我还可以Map
使用System.getenv()
. 但是,调用put()
它Map
会抛出一个UnsupportedOperationException
- 显然它们意味着环境是只读的。而且,没有System.setenv()
。
So, is there any way to set environment variables in the currently running process? If so, how? If not, what's the rationale? (Is it because this is Java and therefore I shouldn't be doing evil nonportable obsolete things like touching my environment?) And if not, any good suggestions for managing the environment variable changes that I'm going to need to be feeding to several subprocesses?
那么,有没有办法在当前运行的进程中设置环境变量呢?如果是这样,如何?如果不是,理由是什么?(是不是因为这是 Java,因此我不应该做一些邪恶的不可移植的过时的事情,比如触摸我的环境?)如果不是,我将需要提供给几个管理环境变量更改的好建议子流程?
采纳答案by Michael Myers
(Is it because this is Java and therefore I shouldn't be doing evil nonportable obsolete things like touching my environment?)
(是不是因为这是 Java,所以我不应该做邪恶的不可移植的过时的事情,比如触摸我的环境?)
I think you've hit the nail on the head.
我想你已经一针见血了。
A possible way to ease the burden would be to factor out a method
减轻负担的一种可能方法是找出一种方法
void setUpEnvironment(ProcessBuilder builder) {
Map<String, String> env = builder.environment();
// blah blah
}
and pass any ProcessBuilder
s through it before starting them.
并ProcessBuilder
在启动它们之前通过它。
Also, you probably already know this, but you can start more than one process with the same ProcessBuilder
. So if your subprocesses are the same, you don't need to do this setup over and over.
此外,您可能已经知道这一点,但是您可以使用相同的ProcessBuilder
. 因此,如果您的子流程相同,则无需一遍又一遍地进行此设置。
回答by matt b
You can pass parameters into your initial java process with -D:
您可以使用 -D 将参数传递到您的初始 java 进程中:
java -cp <classpath> -Dkey1=value -Dkey2=value ...
回答by skiphoppy
Poking around online, it looks like it might be possible to do this with JNI. You'd then have to make a call to putenv() from C, and you'd (presumably) have to do it in a way that worked on both Windows and UNIX.
在网上闲逛,看起来可以用 JNI 来做到这一点。然后,您必须从 C 调用 putenv(),并且您(大概)必须以在 Windows 和 UNIX 上都可以使用的方式来执行此操作。
If all that can be done, it surely wouldn't be too hard for Java itself to support this instead of putting me in a straight Hymanet.
如果所有这些都可以完成,那么 Java 本身支持这一点肯定不会太难,而不是让我穿上直筒夹克。
A Perl-speaking friend elsewhere suggests that this is because environment variables are process global and Java is striving for good isolation for good design.
其他地方的一位讲 Perl 的朋友表示,这是因为环境变量是进程全局变量,而 Java 正在努力为良好的设计提供良好的隔离。
回答by skiphoppy
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);
}
}
}
Or to add/update a single var and removing the loop as per thejoshwolfe's suggestion.
或者根据 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);
}
回答by anonymous
// 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);
}
回答by pushy
For use in scenarios where you need to set specific environment values for unit tests, you might find the following hack useful. It will change the environment variables throughout the JVM (so make sure you reset any changes after your test), but will not alter your system environment.
对于需要为单元测试设置特定环境值的场景,您可能会发现以下 hack 很有用。它将更改整个 JVM 中的环境变量(因此请确保在测试后重置所有更改),但不会更改您的系统环境。
I found that a combination of the two dirty hacks by Edward Campbell and anonymous works best, as one of the does not work under linux, one does not work under windows 7. So to get a multiplatform evil hack I combined them:
我发现 Edward Campbell 和匿名的两个肮脏黑客的组合效果最好,因为其中一个在 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);
}
}
}
}
This Works like a charm. Full credits to the two authors of these hacks.
这就像一个魅力。完全归功于这些黑客的两位作者。
回答by Paul Blair
Tried pushy's answer above and it worked for the most part. However, in certain circumstances, I would see this exception:
在上面尝试了 pushy 的答案,并且在大多数情况下都有效。但是,在某些情况下,我会看到这个例外:
java.lang.String cannot be cast to java.lang.ProcessEnvironment$Variable
This turns out to happen when the method was called more than once, owing to the implementation of certain inner classes of ProcessEnvironment.
If the setEnv(..)
method is called more than once, when the keys are retrieved from the theEnvironment
map, they are now strings (having been put in as strings by the first invocation of setEnv(...)
) and cannot be cast to the map's generic type, Variable,
which is a private inner class of ProcessEnvironment.
事实证明,当该方法被多次调用时会发生这种情况,这是由于某些内部类的实现ProcessEnvironment.
如果该setEnv(..)
方法被多次调用,则当从theEnvironment
映射中检索键时,它们现在是字符串(已放入作为第一次调用setEnv(...)
) 的字符串,并且不能转换为映射的泛型类型,Variable,
这是一个私有的内部类ProcessEnvironment.
A fixed version (in Scala), is below. Hopefully it isn't too difficult to carry over into Java.
下面是一个固定版本(在 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()
}
}
回答by Hans-Christoph Steiner
It turns out that the solution from @pushy/@anonymous/@Edward Campbell does not work on Android because Android is not really Java. Specifically, Android does not have java.lang.ProcessEnvironment
at all. But it turns out to be easier in Android, you just need to do a JNI call to POSIX setenv()
:
事实证明,@pushy/@anonymous/@Edward Campbell 的解决方案不适用于 Android,因为 Android 并不是真正的 Java。具体来说,Android 根本没有java.lang.ProcessEnvironment
。但事实证明在 Android 中更容易,您只需要对 POSIX 进行 JNI 调用setenv()
:
In C/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;
}
And in Java:
在 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);
}
}
回答by user3404318
on Android the interface is exposed via Libcore.os as a kind of hidden API.
在 Android 上,接口通过 Libcore.os 作为一种隐藏的 API 公开。
Libcore.os.setenv("VAR", "value", bOverwrite);
Libcore.os.getenv("VAR"));
The Libcore class as well as the interface OS is public. Just the class declaration is missing and need to be shown to the linker. No need to add the classes to the application, but it also does not hurt if it is included.
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;
}
回答by mangusbrother
This is a combination of @paul-blair 's answer converted to Java which includes some cleanups pointed out by paul blair and some mistakes that seem to have been inside @pushy 's code which is made up of @Edward Campbell and anonymous.
这是@paul-blair 转换为Java 的答案的组合,其中包括paul blair 指出的一些清理以及@pushy 由@Edward Campbell 和匿名组成的代码中似乎存在的一些错误。
I cannot emphasize how much this code should ONLY be used in testing and is extremely hacky. But for cases where you need the environment setup in tests it is exactly what I needed.
我不能强调这段代码应该只在测试中使用多少并且非常hacky。但是对于需要在测试中设置环境的情况,这正是我所需要的。
This also includes some minor touches of mine that allow the code to work on both Windows running on
这也包括我的一些小改动,允许代码在运行的两个 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)
as well as Centos running on
以及运行的 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)
The implementation:
实施:
/**
* 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));
}