409 Stimmen

Wie kann ich in Java Umgebungsvariablen setzen?

Wie kann ich in Java Umgebungsvariablen setzen? Ich sehe, dass ich dies für Unterprozesse tun kann, indem ich ProcessBuilder . Da ich jedoch mehrere Unterprozesse starten muss, würde ich lieber die Umgebung des aktuellen Prozesses ändern und sie an die Unterprozesse weitergeben.

Es gibt eine System.getenv(String) um eine einzelne Umgebungsvariable zu erhalten. Ich kann auch eine Map des vollständigen Satzes von Umgebungsvariablen mit System.getenv() . Aber der Aufruf put() dazu Map wirft einen UnsupportedOperationException -- Sie wollen offenbar, dass die Umgebung nur gelesen werden kann. Und, es gibt keine System.setenv() .

Gibt es also eine Möglichkeit, Umgebungsvariablen im aktuell laufenden Prozess zu setzen? Wenn ja, wie? Wenn nicht, was ist der Grund dafür? (Liegt es daran, dass dies Java ist und ich deshalb keine bösen, nicht portierbaren, veralteten Dinge tun sollte, wie z.B. meine Umgebung anfassen?) Und wenn nicht, gibt es irgendwelche guten Vorschläge für die Verwaltung von Umgebungsvariablenänderungen, die ich an mehrere Unterprozesse weitergeben muss?

278voto

pushy Punkte 9357

Für die Verwendung in Szenarien, in denen Sie bestimmte Umgebungswerte für Unit-Tests festlegen müssen, könnte der folgende Hack nützlich sein. Er ändert die Umgebungsvariablen in der gesamten JVM (stellen Sie also sicher, dass Sie alle Änderungen nach dem Test zurücksetzen), aber er ändert nicht Ihre Systemumgebung.

Ich habe herausgefunden, dass eine Kombination der beiden schmutzigen Hacks von Edward Campbell und anonymous am besten funktioniert, da einer der beiden nicht unter Linux und einer nicht unter Windows 7 funktioniert. Also, um eine Multiplattform böse hack ich sie kombiniert:

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);
      }
    }
  }
}

Das funktioniert wie ein Zauber. Volle Anerkennung für die beiden Autoren dieser Hacks.

109voto

Michael Myers Punkte 183216

(Liegt es daran, dass dies Java ist und ich deshalb keine bösen, nicht portierbaren, veralteten Dinge tun sollte, wie z.B. meine Umgebung zu berühren?)

Ich glaube, Sie haben den Nagel auf den Kopf getroffen.

Ein möglicher Weg, die Belastung zu verringern, wäre, eine Methode zu berücksichtigen

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

und leiten jede ProcessBuilder s durch, bevor sie gestartet werden.

Sie wissen es wahrscheinlich schon, aber Sie können mehrere Prozesse mit demselben ProcessBuilder . Wenn Ihre Unterprozesse dieselben sind, müssen Sie diese Einstellung nicht immer wieder vornehmen.

92voto

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);
        }
    }
}

Oder eine einzelne Variable hinzuzufügen/zu aktualisieren und die Schleife gemäß dem Vorschlag von thejoshwolfe zu entfernen.

@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);
  }

39voto

Hubert Grzeskowiak Punkte 12801

Nur Linux/MacOS

Setzen einzelner Umgebungsvariablen (basierend auf der Antwort von 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);
    }
}

使用することです:

Fügen Sie die Methode zunächst in eine beliebige Klasse ein, z. B. SystemUtil. Dann rufen Sie sie statisch auf:

SystemUtil.setEnv("SHELL", "/bin/bash");

Wenn Sie anrufen System.getenv("SHELL") Danach erhalten Sie "/bin/bash" zurück.

24voto

anonymous Punkte 221
// 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);
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X