392 votes

Comment définir les variables d'environnement à partir de Java ?

Comment définir les variables d'environnement à partir de Java ? Je vois que je peux le faire pour les sous-processus à l'aide des éléments suivants ProcessBuilder . Mais comme j'ai plusieurs sous-processus à lancer, je préfère modifier l'environnement du processus actuel et laisser les sous-processus en hériter.

Il y a un System.getenv(String) pour obtenir une seule variable d'environnement. Je peux également obtenir une Map de l'ensemble complet des variables d'environnement avec System.getenv() . Mais, en appelant put() sur ce point Map lance un UnsupportedOperationException -- apparemment, ils veulent que l'environnement soit en lecture seule. Et, il n'y a pas de System.setenv() .

Existe-t-il un moyen de définir des variables d'environnement dans le processus en cours d'exécution ? Si oui, comment ? Si non, quel est le raisonnement ? (Est-ce parce que c'est Java et donc que je ne devrais pas faire des choses obsolètes non portables comme toucher à mon environnement) ? Et sinon, avez-vous de bonnes suggestions pour gérer les changements de variables d'environnement que je vais devoir transmettre à plusieurs sous-processus ?

1 votes

System.getEnv() est destiné à être universel, certains environnements n'ont même pas de variables d'environnement.

17 votes

Pour tous ceux qui en ont eu besoin dans le cadre de tests unitaires : stackoverflow.com/questions/8168884/

1 votes

19voto

user3404318 Points 11

Sur Android, l'interface est exposée via Libcore.os comme une sorte d'API cachée.

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

La classe Libcore ainsi que l'interface OS sont publiques. Seule la déclaration de la classe est manquante et doit être montrée au linker. Il n'est pas nécessaire d'ajouter les classes à l'application, mais cela ne fait pas de mal non plus de les inclure.

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 votes

Testé et fonctionnant sous Android 4.4.4 (CM11). P.S. Le seul ajustement que j'ai fait a été de remplacer throws ErrnoException con throws Exception .

8 votes

API 21, a Os.setEnv maintenant. [developer.Android.com/reference/Android/system/](http://developer.android.com/reference/android/system/Os.html#setenv(java.lang.String) , java.lang.String, boolean)

1 votes

Potentiellement défunt maintenant avec les nouvelles restrictions de Pie : developer.Android.com/about/versions/pie/

12voto

mangusbrother Points 1677

Il s'agit d'une combinaison de la réponse de @paul-blair convertie en Java, qui comprend certains nettoyages signalés par paul blair, et de quelques erreurs qui semblent avoir été commises dans le code de @pushy, qui est composé de @Edward Campbell et d'anonymous.

Je ne saurais trop insister sur le fait que ce code ne doit être utilisé QUE pour les tests et qu'il est extrêmement compliqué. Mais pour les cas où vous avez besoin de la configuration de l'environnement dans les tests, c'est exactement ce dont j'avais besoin.

Cela inclut également quelques petites retouches de ma part qui permettent au code de fonctionner à la fois sur Windows fonctionnant sous

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)

ainsi que Centos fonctionnant sur

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)

La mise en œuvre :

/**
 * 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

Il s'avère que la solution de @pushy/@anonymous/@Edward Campbell ne fonctionne pas sur Android car Android n'est pas vraiment Java. Plus précisément, Android n'a pas java.lang.ProcessEnvironment du tout. Mais il s'avère que c'est plus facile dans Android, il suffit de faire un appel JNI à POSIX setenv() :

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

Et en 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 Points 468

Comme la plupart des personnes qui ont trouvé ce fil de discussion, j'écrivais des tests unitaires et j'avais besoin de modifier les variables d'environnement afin de définir les conditions correctes pour l'exécution du test. Cependant, j'ai trouvé que les réponses les plus votées présentaient quelques problèmes et/ou étaient très cryptiques ou trop compliquées. J'espère que cela aidera les autres à trouver la solution plus rapidement.

Tout d'abord, j'ai finalement trouvé la solution de @Hubert Grzeskowiak comme étant la plus simple et elle a fonctionné pour moi. J'aurais aimé arriver à cette solution en premier. Elle est basée sur la réponse de @Edward Campbell, mais sans la recherche compliquée de la boucle for.

Cependant, j'ai commencé par la solution de @pushy, qui a obtenu le plus de votes positifs. C'est une combinaison des solutions de @anonymous et de @Edward Campbell. @pushy affirme que les deux approches sont nécessaires pour couvrir les environnements Linux et Windows. Je fonctionne sous OS X et je trouve que les deux fonctionnent (une fois qu'un problème avec l'approche de @anonymous sera résolu). Comme d'autres l'ont noté, cette solution fonctionne la plupart du temps, mais pas toujours.

Je pense que la source de la plupart des confusions vient de la solution de @anonymous qui opère sur le champ 'theEnvironment'. En regardant la définition du champ Environnement du processus l'environnement' n'est pas une Map< String, String > mais plutôt une Map< Variable, Value >. L'effacement de la carte fonctionne bien, mais l'opération putAll reconstruit la carte sous la forme d'une Map< String, String >, ce qui peut poser des problèmes lorsque des opérations ultérieures sont effectuées sur la structure de données à l'aide de l'API normale qui attend une Map< Variable, Value >. De plus, l'accès/la suppression d'éléments individuels pose un problème. La solution consiste à accéder indirectement à 'theEnvironment' par le biais de 'theUnmodifiableEnvironment'. Mais comme il s'agit d'un type UnmodifiableMap l'accès doit se faire par la variable privée 'm' du type UnmodifiableMap. Voir getModifiableEnvironmentMap2 dans le code ci-dessous.

Dans mon cas, j'ai dû supprimer certaines variables d'environnement pour mon test (les autres devraient rester inchangées). Ensuite, je voulais restaurer les variables d'environnement à leur état antérieur après le test. Les routines ci-dessous permettent de le faire facilement. J'ai testé les deux versions de getModifiableEnvironmentMap sous OS X, et les deux fonctionnent de manière équivalente. Cependant, d'après les commentaires de ce fil de discussion, l'une peut être un meilleur choix que l'autre en fonction de l'environnement.

Note : Je n'ai pas inclus l'accès au champ 'theCaseInsensitiveEnvironmentField' car cela semble être spécifique à Windows et je n'avais aucun moyen de le tester, mais l'ajouter devrait être simple.

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

Ashley Frieze Points 1

Il existe trois bibliothèques qui peuvent faire cela pendant les tests unitaires.

Il y a aussi System Rules et System Lambda de Stefan Birkner - https://www.baeldung.com/java-system-rules-junit qui vous permettent de faire quelque chose comme :

public class JUnitTest {

    @Rule
    public EnvironmentVariables environmentVariables = new EnvironmentVariables();

    @Test
    public void someTest() {
        environmentVariables.set("SOME_VARIABLE", "myValue");

        // now System.getenv does what you want
    }
}

ou pour System-Lambda :

@Test
void execute_code_with_environment_variables(
) throws Exception {
  List<String> values = withEnvironmentVariable("first", "first value")
    .and("second", "second value")
    .execute(() -> asList(
      System.getenv("first"),
      System.getenv("second")
    ));
  assertEquals(
    asList("first value", "second value"),
    values
  );
}

Les fonctionnalités ci-dessus sont également disponibles en tant qu'extension de JUnit 5 via les System Stubs :

@ExtendWith(SystemStubsExtension.class)
class SomeTest {

    @SystemStub
    private EnvironmentVariables;

    @Test
    void theTest() {
        environmentVariables.set("SOME_VARIABLE", "myValue");

        // now System.getenv does what you want

    }

}

System Stubs est rétrocompatible avec System Lambda et System Rules, mais prend en charge JUnit 5.

Sinon, il y a aussi JUnit Pioneer - https://github.com/junit-pioneer/junit-pioneer qui permet de définir des variables d'environnement au moment du test par le biais d'annotations.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X