Wie setze ich Umgebungsvariablen von Java aus?


289

Wie setze ich Umgebungsvariablen von Java aus? Ich sehe, dass ich dies für Unterprozesse mit tun kann ProcessBuilder. Ich muss jedoch mehrere Unterprozesse starten, daher möchte ich lieber die Umgebung des aktuellen Prozesses ändern und die Unterprozesse diese erben lassen.

Es gibt eine System.getenv(String), um eine einzelne Umgebungsvariable zu erhalten. Ich kann auch einen Mapvollständigen Satz von Umgebungsvariablen mit abrufen System.getenv(). Aber, ruft put()auf , dass Mapwirft ein UnsupportedOperationException- offenbar bedeuten sie für die Umwelt nur gelesen werden. Und es gibt keine System.setenv().

Gibt es also eine Möglichkeit, Umgebungsvariablen im aktuell ausgeführten Prozess festzulegen? Wenn das so ist, wie? Wenn nicht, was ist der Grund? (Liegt es daran, dass dies Java ist und ich daher keine bösen, nicht portierbaren, veralteten Dinge wie das Berühren meiner Umgebung tun sollte?) Und wenn nicht, gibt es gute Vorschläge für die Verwaltung der Änderungen der Umgebungsvariablen, die ich mehreren zuführen muss Unterprozesse?


System.getEnv () soll universell sein, einige Umgebungen haben nicht einmal Umgebungsvariablen.
b1nary.atr0phy

7
Für alle, die dies für einen Unit-Test-Anwendungsfall benötigten: stackoverflow.com/questions/8168884/…
Atifm

Verwenden Sie für Scala Folgendes
Vlad Patryshev

Antworten:


88

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

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

Ein möglicher Weg, um die Belastung zu verringern, wäre das Herausrechnen einer Methode

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

und führen Sie alle ProcessBuilders durch, bevor Sie sie starten.

Wahrscheinlich wissen Sie das auch schon, aber Sie können mehr als einen Prozess mit demselben starten ProcessBuilder. Wenn Ihre Unterprozesse identisch sind, müssen Sie dieses Setup nicht immer wieder durchführen.


1
Es ist eine Schande, dass das Management nicht zulässt, dass ich eine andere tragbare Sprache verwende, um diese Reihe von bösen, veralteten Unterprozessen auszuführen. :)
Skiphoppy

18
S.Lott, ich möchte nicht die Umgebung eines Elternteils festlegen. Ich möchte meine eigene Umgebung einrichten.
Skiphoppy

3
Das funktioniert großartig, es sei denn, es ist die Bibliothek eines anderen (z. B. die von Sun), die den Prozess startet.
Sullivan

24
@ b1naryatr0phy Du hast den Punkt verpasst. Niemand kann mit Ihren Umgebungsvariablen spielen, da diese Variablen für einen Prozess lokal sind (was Sie in Windows festlegen, sind die Standardwerte). Jeder Prozess kann seine eigenen Variablen ändern ... es sei denn, es handelt sich um Java.
Maaartinus

9
Diese Einschränkung von Java ist ein bisschen wie ein Cop out. Es gibt keinen Grund, warum Sie in Java keine anderen Umgebungsvariablen festlegen dürfen als "weil wir nicht möchten, dass Java dies tut".
IanNorton

232

Für die Verwendung in Szenarien, in denen Sie bestimmte Umgebungswerte für Komponententests festlegen müssen, ist der folgende Hack möglicherweise hilfreich. Dadurch werden die Umgebungsvariablen in der gesamten JVM geändert (stellen Sie daher sicher, dass Sie alle Änderungen nach dem Test zurücksetzen), Ihre Systemumgebung wird jedoch nicht geändert.

Ich fand heraus, dass eine Kombination der beiden schmutzigen Hacks von Edward Campbell und anonym am besten funktioniert, da einer unter Linux nicht funktioniert, einer unter Windows 7 nicht. Um einen bösen Hack mit mehreren Plattformen zu erhalten, habe 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 Credits an die beiden Autoren dieser Hacks.


1
Wird sich dies nur im Speicher ändern oder wird tatsächlich die gesamte Umgebungsvariable im System geändert?
Shervin Asgari

36
Dadurch wird nur die Umgebungsvariable im Speicher geändert. Dies ist gut zum Testen geeignet, da Sie die Umgebungsvariable nach Bedarf für Ihren Test festlegen können, die Envs jedoch im System unverändert lassen. Tatsächlich würde ich jeden davon abhalten, diesen Code für andere Zwecke als zum Testen zu verwenden. Dieser Code ist böse ;-)
aufdringlich

9
Als FYI erstellt die JVM beim Start eine Kopie der Umgebungsvariablen. Dadurch wird diese Kopie bearbeitet, nicht die Umgebungsvariablen für den übergeordneten Prozess, der die JVM gestartet hat.
Bmeding

Ich habe es auf Android versucht und es schien nicht zu dauern. Hat noch jemand Glück auf Android?
Hans-Christoph Steiner

5
Sicher,import java.lang.reflect.Field;
aufdringlich

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

3
Es hört sich so an, als würde dies die Karte im Speicher ändern, aber würde es den Wert im System speichern?
Jon Onstott

1
Nun, es ändert die Speicherzuordnung von Umgebungsvariablen. Ich denke, das reicht in vielen Anwendungsfällen aus. @ Edward - Meine Güte, es ist schwer vorstellbar, wie diese Lösung überhaupt herausgefunden wurde!
Anirvan

13
Dadurch werden die Umgebungsvariablen auf dem System nicht geändert, sie werden jedoch beim aktuellen Aufruf von Java geändert. Dies ist sehr nützlich für Unit-Tests.
Stuart K

10
warum nicht Class<?> cl = env.getClass();stattdessen for-Schleife verwenden?
Thejoshwolfe

1
Genau das habe ich gesucht! Ich habe Integrationstests für Code geschrieben, der ein Tool eines Drittanbieters verwendet, mit dem Sie aus irgendeinem Grund nur die absurd kurze Standard-Timeout-Länge mit einer Umgebungsvariablen ändern können.
David DeMar

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

17

Unter Android wird die Benutzeroberfläche über Libcore.os als eine Art versteckte API verfügbar gemacht.

Libcore.os.setenv("VAR", "value", bOverwrite);
Libcore.os.getenv("VAR"));

Die Libcore-Klasse sowie das Schnittstellenbetriebssystem sind öffentlich. Nur die Klassendeklaration fehlt und muss dem Linker angezeigt werden. Es ist nicht erforderlich, die Klassen zur Anwendung hinzuzufügen, aber es tut auch nicht weh, wenn sie enthalten ist.

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

1
Getestet und unter Android 4.4.4 (CM11). PS Die einzige Einstellung, die ich vorgenommen habe, war das Ersetzen throws ErrnoExceptiondurch throws Exception.
DavisNT

7
API 21 hat Os.setEnvjetzt. developer.android.com/reference/android/system/… , java.lang.String, boolean)
Jared Burrows

1
Möglicherweise nicht mehr
verfügbar

13

Nur Linux

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

Verwendungszweck:

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

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

Wenn Sie danach anrufen System.getenv("SHELL"), werden Sie "/bin/bash"zurückkommen.


Das Obige funktioniert nicht unter Windows 10, aber unter Linux.
Mengchengfeng

Interessant. Ich habe es nicht selbst unter Windows versucht. Erhalten Sie eine Fehlermeldung, @mengchengfeng?
Hubert Grzeskowiak

@ HubertGrzeskowiak Wir haben keine Fehlermeldungen gesehen, es hat einfach nicht funktioniert ...
mengchengfeng

9

Dies ist eine Kombination aus der in Java konvertierten Antwort von @ paul-blair, die einige Bereinigungen enthält, auf die paul blair hingewiesen hat, und einige Fehler, die in @pushys Code enthalten zu sein scheinen, der aus @Edward Campbell besteht und anonym ist.

Ich kann nicht betonen, wie oft dieser Code NUR zum Testen verwendet werden sollte und ist extrem hackig. Aber für Fälle, in denen Sie die Umgebung in Tests einrichten müssen, ist es genau das, was ich brauchte.

Dies beinhaltet auch einige kleinere Details von mir, die es dem Code ermöglichen, unter beiden Windows-Betriebssystemen zu funktionieren

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)

sowie Centos laufen weiter

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)

Die Umsetzung:

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

7

Es stellt sich heraus, dass die Lösung von @ pushy / @ anonym / @ Edward Campbell unter Android nicht funktioniert, da Android nicht wirklich Java ist. Insbesondere hat Android überhaupt nicht java.lang.ProcessEnvironment. In Android ist dies jedoch einfacher. Sie müssen lediglich einen JNI-Aufruf an POSIX sendensetenv() :

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

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

5

Wie die meisten Leute, die diesen Thread gefunden haben, habe ich einige Komponententests geschrieben und musste die Umgebungsvariablen ändern, um die richtigen Bedingungen für die Ausführung des Tests festzulegen. Ich fand jedoch, dass die am besten bewerteten Antworten einige Probleme hatten und / oder sehr kryptisch oder übermäßig kompliziert waren. Hoffentlich hilft dies anderen, die Lösung schneller zu finden.

Als erstes fand ich die Lösung von @Hubert Grzeskowiak am einfachsten und sie funktionierte für mich. Ich wünschte, ich wäre zuerst dazu gekommen. Es basiert auf der Antwort von @Edward Campbell, ohne jedoch die Suche nach Schleifen zu erschweren.

Ich habe jedoch mit der Lösung von @ pushy begonnen, die die meisten positiven Stimmen erhielt. Es ist eine Kombination aus @anonymous und @Edward Campbell's. @pushy behauptet, dass beide Ansätze erforderlich sind, um sowohl Linux- als auch Windows-Umgebungen abzudecken. Ich laufe unter OS X und stelle fest, dass beide funktionieren (sobald ein Problem mit dem @ anonymous-Ansatz behoben ist). Wie andere angemerkt haben, funktioniert diese Lösung die meiste Zeit, aber nicht alle.

Ich denke, die Ursache für den größten Teil der Verwirrung liegt in der Lösung von @onymous, die im Feld "theEnvironment" ausgeführt wird. Bei der Definition der ProcessEnvironment Struktur ist 'theEnvironment' keine Map <String, String>, sondern eine Map <Variable, Value>. Das Löschen der Map funktioniert einwandfrei, aber die putAll-Operation erstellt die Map neu als Map <String, String>, was möglicherweise zu Problemen führt, wenn nachfolgende Operationen die Datenstruktur mit der normalen API bearbeiten, die Map <Variable, Wert> erwartet. Auch der Zugriff auf / das Entfernen einzelner Elemente ist ein Problem. Die Lösung besteht darin, indirekt über 'die nicht veränderbare Umgebung' auf 'theEnvironment' zuzugreifen. Da dies aber ein Typ UnmodizableMap ist muss der Zugriff über die private Variable 'm' vom Typ UnmodizableMap erfolgen. Siehe getModutableEnvironmentMap2 im folgenden Code.

In meinem Fall musste ich einige der Umgebungsvariablen für meinen Test entfernen (die anderen sollten unverändert bleiben). Dann wollte ich die Umgebungsvariablen nach dem Test auf ihren vorherigen Zustand zurücksetzen. Die folgenden Routinen machen dies einfach. Ich habe beide Versionen von getModutableEnvironmentMap unter OS X getestet und beide funktionieren gleichwertig. Obwohl dies auf Kommentaren in diesem Thread basiert, ist je nach Umgebung möglicherweise eine bessere Wahl als die andere.

Hinweis: Ich habe keinen Zugriff auf das 'theCaseInsensitiveEnvironmentField' hinzugefügt, da dies Windows-spezifisch zu sein scheint und ich keine Möglichkeit hatte, es zu testen, aber das Hinzufügen sollte einfach sein.

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

Danke, es war genau mein Anwendungsfall und auch unter Mac OS X.
Rafael Gonçalves

Das hat mir so gut gefallen, dass ich eine etwas einfachere Version für Groovy entwickelt habe, siehe unten.
Mike Nagetier

4

Wenn Sie online stöbern, scheint es möglich zu sein, dies mit JNI zu tun. Sie müssten dann putenv () von C aus aufrufen, und Sie müssten dies (vermutlich) auf eine Weise tun, die sowohl unter Windows als auch unter UNIX funktioniert.

Wenn all das getan werden kann, wäre es für Java selbst sicherlich nicht zu schwierig, dies zu unterstützen, anstatt mich in eine Zwangsjacke zu stecken.

Ein Perl sprechender Freund an anderer Stelle schlägt vor, dass dies darauf zurückzuführen ist, dass Umgebungsvariablen prozessglobal sind und Java eine gute Isolation für ein gutes Design anstrebt.


Ja, Sie können die Prozessumgebung über C-Code festlegen. Aber ich würde nicht damit rechnen, dass das in Java funktioniert. Es besteht eine gute Chance, dass die JVM die Umgebung beim Start in Java-String-Objekte kopiert, sodass Ihre Änderungen nicht für zukünftige JVM-Vorgänge verwendet werden.
Darron

Danke für die Warnung, Darron. Es besteht wahrscheinlich eine gute Chance, dass Sie Recht haben.
Skiphoppy

2
@Darron Viele der Gründe, warum man dies tun möchte, haben überhaupt nichts mit dem zu tun, was die JVM für die Umgebung hält. (Denken Sie an die Einstellung LD_LIBRARY_PATHvor dem Aufruf Runtime.loadLibrary(). Der dlopen()aufgerufene Aufruf bezieht sich auf die reale Umgebung und nicht auf Javas Vorstellung davon.)
Charles Duffy

Dies funktioniert für Unterprozesse, die von einer nativen Bibliothek gestartet wurden (in meinem Fall die meisten von ihnen), funktioniert jedoch leider nicht für Unterprozesse, die von Javas Process- oder ProcessBuilder-Klassen gestartet wurden.
Dan

4

Versuchte Pushys Antwort oben und es funktionierte größtenteils. Unter bestimmten Umständen würde ich jedoch diese Ausnahme sehen:

java.lang.String cannot be cast to java.lang.ProcessEnvironment$Variable

Dies stellt sich heraus, wenn die Methode aufgrund der Implementierung bestimmter innerer Klassen von mehr als einmal aufgerufen wurde. ProcessEnvironment.Wenn die setEnv(..)Methode mehr als einmal aufgerufen wird und die Schlüssel aus der theEnvironmentMap abgerufen werden , handelt es sich nun um Zeichenfolgen (die eingegeben wurden) als Zeichenfolgen beim ersten Aufruf von setEnv(...)) und kann nicht in den generischen Typ der Karte umgewandelt werden, Variable,der eine private innere Klasse von istProcessEnvironment.

Eine feste Version (in Scala) finden Sie weiter unten. Hoffentlich ist es nicht allzu schwierig, auf Java zu übertragen.

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

Wo ist JavaClass definiert?
Mike Slinn

1
Vermutlich import java.lang.{Class => JavaClass}.
Randall Whitman

1
Die Implementierung von java.lang.ProcessEnvironment ist auf verschiedenen Plattformen unterschiedlich, selbst für denselben Build. Beispielsweise gibt es in der Implementierung von Windows keine Klasse java.lang.ProcessEnvironment $ Variable, aber diese Klasse ist in einer für Linux vorhanden. Sie können es leicht überprüfen. Laden Sie einfach die JDK-Distribution tar.gz für Linux herunter, extrahieren Sie die Quelle aus src.zip und vergleichen Sie sie mit derselben Datei aus der Distribution für Windows. Sie sind in JDK 1.8.0_181 völlig unterschiedlich. Ich habe sie in Java 10 nicht überprüft, aber ich werde nicht überrascht sein, wenn es das gleiche Bild gibt.
Alex Konshin

1

Dies ist die böse Kotlin-Version der bösen Antwort von @ pushy =)

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

Es funktioniert zumindest in macOS Mojave.


0

Wenn Sie mit SpringBoot arbeiten, können Sie die Angabe der Umgebungsvariablen in der folgenden Eigenschaft hinzufügen:

was.app.config.properties.toSystemProperties

1
Können Sie bitte etwas erklären?
Faraz

0

Variante basierend auf der Antwort von @ pushy , funktioniert unter Windows.

def set_env(newenv):
    from java.lang import Class
    process_environment = Class.forName("java.lang.ProcessEnvironment")
    environment_field =  process_environment.getDeclaredField("theEnvironment")
    environment_field.setAccessible(True)
    env = environment_field.get(None)
    env.putAll(newenv)
    invariant_environment_field = process_environment.getDeclaredField("theCaseInsensitiveEnvironment");
    invariant_environment_field.setAccessible(True)
    invevn = invariant_environment_field.get(None)
    invevn.putAll(newenv)

Verwendungszweck:

old_environ = dict(os.environ)
old_environ['EPM_ORACLE_HOME'] = r"E:\Oracle\Middleware\EPMSystem11R1"
set_env(old_environ)

0

Tim Ryans Antwort hat bei mir funktioniert ... aber ich wollte sie für Groovy (zum Beispiel Spock-Kontext) und simplissimo:

import java.lang.reflect.Field

def getModifiableEnvironmentMap() {
    def unmodifiableEnv = System.getenv()
    Class cl = unmodifiableEnv.getClass()
    Field field = cl.getDeclaredField("m")
    field.accessible = true
    field.get(unmodifiableEnv)
}

def clearEnvironmentVars( def keys ) {
    def savedVals = [:]
    keys.each{ key ->
        String val = modifiableEnvironmentMap.remove(key)
        // thinking about it, I'm not sure why we need this test for null
        // but haven't yet done any experiments
        if( val != null ) {
            savedVals.put( key, val )
        }
    }
    savedVals
}

def setEnvironmentVars(Map varMap) {
    modifiableEnvironmentMap.putAll(varMap)
}

// pretend existing Env Var doesn't exist
def PATHVal1 = System.env.PATH
println "PATH val1 |$PATHVal1|"
String[] keys = ["PATH", "key2", "key3"]
def savedVars = clearEnvironmentVars(keys)
def PATHVal2 = System.env.PATH
println "PATH val2 |$PATHVal2|"

// return to reality
setEnvironmentVars(savedVars)
def PATHVal3 = System.env.PATH
println "PATH val3 |$PATHVal3|"
println "System.env |$System.env|"

// pretend a non-existent Env Var exists
setEnvironmentVars( [ 'key4' : 'key4Val' ])
println "key4 val |$System.env.key4|"

0

In einer Version in Kotlin habe ich in diesem Algorithmus einen Dekorator erstellt, mit dem Sie Variablen festlegen und aus der Umgebung abrufen können.

import java.util.Collections
import kotlin.reflect.KProperty

class EnvironmentDelegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return System.getenv(property.name) ?: "-"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        val key = property.name

        val classes: Array<Class<*>> = Collections::class.java.declaredClasses
        val env = System.getenv()

        val cl = classes.first { "java.util.Collections\$UnmodifiableMap" == it.name }

        val field = cl.getDeclaredField("m")
        field.isAccessible = true
        val obj = field[env]
        val map = obj as MutableMap<String, String>
        map.putAll(mapOf(key to value))
    }
}

class KnownProperties {
    var JAVA_HOME: String by EnvironmentDelegate()
    var sample: String by EnvironmentDelegate()
}

fun main() {
    val knowProps = KnownProperties()
    knowProps.sample = "2"

    println("Java Home: ${knowProps.JAVA_HOME}")
    println("Sample: ${knowProps.sample}")
}

-1

Kotlin-Implementierung, die ich kürzlich basierend auf Edwards Antwort vorgenommen habe:

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

-12

Sie können Parameter mit -D an Ihren anfänglichen Java-Prozess übergeben:

java -cp <classpath> -Dkey1=value -Dkey2=value ...

Die Werte sind zur Ausführungszeit nicht bekannt. Sie werden während der Programmausführung bekannt, wenn der Benutzer sie bereitstellt / auswählt. Und das legt nur Systemeigenschaften fest, keine Umgebungsvariablen.
Skiphoppy

In diesem Fall möchten Sie wahrscheinlich einen regulären Weg finden (über den Parameter args [] zur Hauptmethode), um Ihre Unterprozesse aufzurufen.
Matt B

matt b, der normale Weg ist über ProcessBuilder, wie in meiner ursprünglichen Frage erwähnt. :)
Skiphoppy

7
-D Parameter sind über verfügbar System.getPropertyund nicht identisch mit System.getenv. Neben demSystem erlaubt Klasse auch, diese Eigenschaften statisch setProperty
festzulegen,
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.