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?

21voto

user3404318 Punkte 181

Unter Android wird die Schnittstelle über Libcore.os als eine Art versteckte API offengelegt.

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

Sowohl die Klasse Libcore als auch die Schnittstelle OS sind öffentlich. Nur die Klassendeklaration fehlt und muss dem Linker angezeigt werden. Es ist nicht nötig, die Klassen zur Anwendung hinzuzufügen, aber es schadet auch nicht, wenn sie enthalten sind.

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

12voto

mangusbrother Punkte 3704

Dies ist eine Kombination aus der Antwort von @paul-blair, die nach Java konvertiert wurde und einige Bereinigungen enthält, auf die Paul Blair hingewiesen hat, und einigen Fehlern, die anscheinend im Code von @pushy enthalten waren, der von @Edward Campbell und anonymous erstellt wurde.

Ich kann nicht betonen, wie sehr dieser Code NUR zu Testzwecken verwendet werden sollte und extrem hakelig ist. Aber für Fälle, in denen Sie die Umgebung Setup in Tests benötigen, ist es genau das, was ich brauchte.

Dazu gehören auch einige kleinere Verbesserungen von mir, die es ermöglichen, dass der Code sowohl unter Windows als auch unter

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, das auf

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

10voto

Es stellt sich heraus, dass die Lösung von @pushy/@anonymous/@Edward Campbell unter Android nicht funktioniert, weil Android nicht wirklich Java ist. Genauer gesagt, hat Android keine java.lang.ProcessEnvironment überhaupt nicht. Aber es stellt sich heraus, dass es in Android einfacher ist, man muss nur einen JNI-Aufruf an POSIX machen setenv() :

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

9voto

Tim Ryan Punkte 990

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

Zunächst einmal habe ich die Lösung von @Hubert Grzeskowiak als die einfachste gefunden, und sie hat bei mir funktioniert. Ich wünschte, ich wäre zuerst auf diese Lösung gekommen. Sie basiert auf der Antwort von @Edward Campbell, aber ohne die komplizierte for-Schleifensuche.

Ich habe jedoch mit der Lösung von @pushy begonnen, die die meisten Bewertungen erhalten hat. Sie ist eine Kombination aus der von @anonymous und @Edward Campbell. @pushy behauptet, dass beide Ansätze erforderlich sind, um sowohl Linux- als auch Windows-Umgebungen abzudecken. Ich arbeite unter OS X und finde, dass beide funktionieren (sobald ein Problem mit @anonymous Ansatz behoben ist). Wie andere festgestellt haben, funktioniert diese Lösung meistens, aber nicht immer.

Ich denke, der Grund für die meisten Verwirrungen liegt in der Lösung von @anonymous, die mit dem Feld "theEnvironment" arbeitet. Wenn man sich die Definition des Feldes ProzessUmgebung Struktur ist "theEnvironment" keine Map< String, String >, sondern eine Map< Variable, Value >. Das Löschen der Map funktioniert gut, aber die PutAll-Operation baut die Map als Map< String, String > neu auf, was zu Problemen führen kann, wenn nachfolgende Operationen die Datenstruktur mit der normalen API bearbeiten, die Map< Variable, Value > erwartet. Auch der Zugriff auf einzelne Elemente und deren Entfernung ist ein Problem. Die Lösung besteht darin, auf "theEnvironment" indirekt über "theUnmodifiableEnvironment" zuzugreifen. Da dies aber ein Typ ist UnveränderbarMap der Zugriff muss über die private Variable 'm' vom Typ UnmodifiableMap erfolgen. Siehe getModifiableEnvironmentMap2 im nachfolgenden 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 wieder in ihren vorherigen Zustand zurückversetzen. Mit den nachstehenden Routinen ist das ganz einfach möglich. Ich habe beide Versionen von getModifiableEnvironmentMap unter OS X getestet, und beide funktionieren gleichwertig. Basierend auf den Kommentaren in diesem Thread könnte die eine Version je nach Umgebung besser geeignet sein als die andere.

Hinweis: Ich habe den Zugriff auf das "theCaseInsensitiveEnvironmentField" nicht mit einbezogen, da es 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);
}

8voto

deddu Punkte 757

Wenn Sie wie ich mit diesem Problem beim Testen konfrontiert sind und Junit5 verwenden, Junit-Pionier ist mit sehr hilfreichen Kommentaren versehen. Maven-Freigabe

Beispiel:

@Test
@SetEnvironmentVariable(key = "some variable",value = "new value")
void test() {
    assertThat(System.getenv("some variable")).
        isEqualTo("new value");
}

kann es nicht genug empfehlen.

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