Javaから環境変数を設定する方法 ProcessBuilder
を使って、サブプロセスでこれができることがわかります。ただし、開始するサブプロセスがいくつかあるので、現在のプロセスの環境を変更して、サブプロセスに継承させます。
単一の環境変数を取得するためのSystem.getenv(String)
があります。 System.getenv()
を使って、環境変数の完全なセットのMap
を取得することもできます。しかし、そのMap
でput()
を呼び出すと、UnsupportedOperationException
がスローされます。明らかに、それらは環境が読み取り専用であることを意味します。そして、System.setenv()
はありません。
それで、現在実行中のプロセスで環境変数を設定する方法はありますか?もしそうなら、どうですか?そうでなければ、その論理的根拠は何ですか? (これはJavaであり、したがって私は自分の環境に触れることのような邪魔な移植不可能なことをしてはいけません。)そしてそうでなければ、環境変数の変更を管理するためにサブプロセス?
(これはJavaであり、したがって私は自分の環境に触れるなどの邪魔になる移植不可能なことをしてはいけません。
頭に釘を打ったと思います。
負担を軽減するための考えられる方法は、メソッドを除外することです。
void setUpEnvironment(ProcessBuilder builder) {
Map<String, String> env = builder.environment();
// blah blah
}
そしてそれらを開始する前にそれを通してどんなProcessBuilder
も渡してください。
また、あなたはすでにこれを知っているかもしれませんが、同じProcessBuilder
で複数のプロセスを起動することができます。したがって、サブプロセスが同じであれば、この設定を何度も繰り返す必要はありません。
単体テストのために特定の環境値を設定する必要があるシナリオでの使用には、以下のハックが役に立つかもしれません。 JVM全体で環境変数が変更されます(したがって、テスト後に必ず変更をリセットしてください)が、システム環境は変更されません。
Edward Campbellによる2つのダーティーハックと匿名の組み合わせが最もうまくいくことがわかりました。そのうちの1つはlinuxでは動作せず、1つは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);
}
}
}
}
これは魅力のように動作します。これらのハックの2人の作者への完全なクレジット。
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);
}
}
}
あるいは、1つの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では、インターフェースは一種の隠しAPIとしてLibcore.osを介して公開されています。
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;
}
単一の環境変数を設定する(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"
が返されます。
Androidは実際にはJavaではないため、@ pushy/@ anonymous/@ Edward Campbellによる解決策はAndroid上では機能しません。具体的には、AndroidにはJava.lang.ProcessEnvironment
がまったくありません。しかし、Androidではより簡単であることがわかります。POSIXsetenv()
に対して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);
}
}
これは、paul blairによって指摘されたいくつかのクリーンアップと@Edward Campbellと匿名で構成されていると思われるいくつかの間違いを含む、@ paul-blairの答えをJavaに変換したものです。
私はこのコードがテストで使用されるべき量を強調することはできず、非常にハックです。しかし、テストで環境設定が必要な場合は、まさに私が必要としていたものです。
これはまた、コードがWindows上で実行されている両方の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));
}
このスレッドを見つけたほとんどの人と同じように、私はいくつかのユニットテストを書いていて、テストを実行するための正しい条件を設定するために環境変数を修正する必要がありました。しかし、私は最も支持された回答にいくつかの問題があること、そして/または非常に謎めいているか過度に複雑であることを発見しました。うまくいけば、これは他の人がより迅速に解決策を整理するのに役立ちます。
最初に、@ Hubert Grzeskowiakのソリューションが一番簡単であることがようやくわかりました。私はその方に最初に来てもらえればと思います。これは@Edward Campbellの答えに基づいていますが、ループ検索を複雑にすることはありません。
しかし、私は@ pushyの解決策から始めました。 @anonymousと@Edward Campbellのコンボです。 @pushyは、LinuxとWindowsの両方の環境をカバーするには両方のアプローチが必要であると主張しています。私はOS Xの下で走っていて、両方ともうまくいくことがわかりました(@anonymousアプローチの問題が修正されたら)。他の人が指摘したように、この解決策はほとんどの場合うまくいくが、すべてではない。
私は混乱の大部分の原因は@ anonymousのソリューションが 'theEnvironment'フィールドを操作していることにあると思います。 ProcessEnvironment 構造体の定義を見ると、 'theEnvironment'はMap <String、String>ではなく、Map <Variable、Value>です。マップをクリアしても問題はありませんが、putAll操作によってマップがMap <String、String>に再構築されるため、その後の操作でMap <Variable、Value>を想定する通常のAPIを使用してデータ構造を操作するときに問題が生じる可能性があります。また、個々の要素にアクセスしたり削除したりすることも問題です。解決策は、「theUnmodifiableEnvironment」を介して間接的に「theEnvironment」にアクセスすることです。しかし、これは型 nmodifiableMap なので、アクセスはUnmodifiableMap型のプライベート変数 'm'を通して行わなければなりません。以下のコードの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);
}
オンラインで覗いてみると、JNIを使用してこれを実行できる可能性があります。それから、Cからputenv()への呼び出しをしなければなりません、そして、あなたは(おそらく)WindowsとUNIXの両方でうまくいった方法でそれをしなければならないでしょう。
それがすべてできるとしたら、私をまっすぐなジャケットにまとめるのではなく、Java自体がこれをサポートするのは難しくありません。
他のPerlを話す友人は、これは環境変数がプロセスグローバルであり、Javaが優れた設計のための優れた分離のために努力しているためであると示唆しています。
上記の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()
}
}
これは@ pushyの悪の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で動いています。
Spring Bootを使用する場合は、次のプロパティで環境変数を指定して追加できます。
was.app.config.properties.toSystemProperties