354

Java から環境変数を設定するにはどうすればよいですか? を使用してサブプロセスに対してこれを実行できることがわかりましたProcessBuilder。ただし、開始するサブプロセスがいくつかあるので、現在のプロセスの環境を変更して、サブプロセスに継承させたいと思います。

System.getenv(String)単一の環境変数を取得するための があります。Mapを使用して、環境変数の完全なセットを取得することもできますSystem.getenv()。しかし、それを呼び出すとput()Map環境UnsupportedOperationExceptionが読み取り専用になることを意味しているようです。そして、ありませんSystem.setenv()

では、現在実行中のプロセスで環境変数を設定する方法はありますか? もしそうなら、どのように?そうでない場合、その根拠は何ですか?(これは Java であるため、環境に触れるなど、移植性のない古いものを実行するべきではありませんか?) そうでない場合は、環境変数の変更を管理するための良い提案があれば、いくつかにフィードする必要があります。サブプロセス?

4

23 に答える 23

257

単体テスト用に特定の環境値を設定する必要があるシナリオで使用するには、次のハックが役立つ場合があります。JVM全体で環境変数が変更されますが(テスト後に変更をリセットしてください)、システム環境は変更されません。

Edward Campbell と anonymous による 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 人の作者に完全なクレジットを。

于 2011-08-26T08:17:03.520 に答える
101

(これはJavaであるため、環境に触れるなど、移植性のない邪悪な時代遅れのことをするべきではないからですか?)

頭に釘を打ったと思います。

負担を軽減するための可能な方法は、方法を除外することです

void setUpEnvironment(ProcessBuilder builder) {
    Map<String, String> env = builder.environment();
    // blah blah
}

ProcessBuilder開始する前に、sを通過させます。

また、おそらくすでにこれを知っていますが、同じで複数のプロセスを開始できますProcessBuilder。したがって、サブプロセスが同じである場合、このセットアップを何度も行う必要はありません。

于 2008-11-25T17:45:04.037 に答える
29

Linux/MacOS のみ

単一の環境変数の設定 (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"折り返します。

于 2016-11-18T16:38:29.227 に答える
22
// 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);
}
于 2010-12-20T07:45:41.723 に答える
11

これは、Java に変換された @paul-blair の回答の組み合わせです。これには、paul blair によって指摘されたいくつかのクリーンアップと、@Edward Campbell および匿名で構成される @pushy のコード内にあったと思われるいくつかの間違いが含まれます。

このコードがテストでのみ使用されるべきであり、非常にハックであることを強調することはできません。しかし、テストで環境設定が必要な場合は、まさに私が必要としていたものです。

これには、実行中の両方の 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));
}
于 2016-06-28T10:35:05.743 に答える
4

オンラインで調べてみると、JNIでこれを行うことができるようです。次に、Cからputenv()を呼び出す必要があり、(おそらく)WindowsとUNIXの両方で機能する方法で呼び出す必要があります。

それがすべてできれば、私を拘束衣に入れる代わりに、Java自体がこれをサポートするのはそれほど難しいことではないでしょう。

他の場所でPerlを話す友人は、これは環境変数がプロセスグローバルであり、Javaが優れた設計のために優れた分離を目指しているためだと示唆しています。

于 2008-11-25T20:59:02.210 に答える
4

上記の強引な答えを試してみましたが、ほとんどの場合うまくいきました。ただし、特定の状況では、次の例外が表示されます。

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()
  }
}
于 2013-09-27T00:01:53.480 に答える
-13

-Dを使用して、最初のJavaプロセスにパラメーターを渡すことができます。

java -cp <classpath> -Dkey1=value -Dkey2=value ...
于 2008-11-25T17:42:09.253 に答える