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 faire cela pour les sous-processus en utilisant ProcessBuilder . J'ai plusieurs sous-processus à démarrer, donc je préfère modifier l'environnement du processus actuel et laisser les sous-processus en hériter.

il y a un système.getenv (String) pour obtenir une seule variable d'environnement. Je peux également obtenir une carte de L'ensemble complet des variables d'environnement avec le système.la fonction getenv(). Mais l'appel mettre () sur cette carte jette une exception D'opération non supportée -- apparemment ils signifient que l'environnement doit être lu seulement. Et il n'y a pas de système.setenv ().

alors, y a-t-il un moyen de définir des variables d'environnement dans le processus en cours? Si oui, comment? Si non, quelle est la justification? (Est-ce parce que C'est Java et donc je ne devrais pas faire de mauvaises choses non-durables et obsolètes comme toucher mon environnement?) Et si non, toutes les bonnes suggestions pour la gestion de l'environnement des changements de variables que je vais devoir alimenter à plusieurs sous-processus?

228
demandé sur hennr 2008-11-25 20:38:19

13 réponses

(est-ce parce que C'est Java et donc je ne devrais pas faire de mauvaises choses obsolètes non-durables comme toucher mon environnement?)

je pense que vous avez touché le clou sur la tête.

Une manière d'alléger le fardeau serait facteur d'une méthode

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

et passer tous les ProcessBuilder avant de les Démarrer.

Aussi, vous le savez probablement déjà cela, mais vous pouvez commencer à plus d'un processus avec le même ProcessBuilder . Donc, si votre sous-processus sont les mêmes, vous n'avez pas besoin de faire cette installation.

77
répondu Michael Myers 2008-11-25 17:56:47

pour une utilisation dans des scénarios où vous avez besoin de définir des valeurs d'environnement spécifiques pour les tests unitaires, vous pourriez trouver le hack suivant utile. Il modifiera les variables d'environnement tout au long de la JVM (assurez-vous donc de réinitialiser tout changement après votre test), mais ne modifiera pas l'environnement de votre système.

j'ai trouvé qu'une combinaison des deux sales hacks par Edward Campbell et anonymous fonctionne mieux, comme l'un des ne fonctionne pas sous linux, on ne fonctionne pas sous windows 7. Donc pour obtenir un hack diabolique multiplatform je les ai combinés:

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

Cela Fonctionne comme un charme. Tous les crédits aux deux auteurs de ces hacks.

183
répondu pushy 2017-09-26 03:47:38
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);
        }
    }
}
43
répondu 2009-01-30 19:24:51

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 classe est manquante et doit être montrée au linker. Pas besoin d'ajouter les classes à l'application, mais cela ne fait pas de mal non plus si elle est incluse.

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;
}
16
répondu user3404318 2014-03-11 02:45:51
// 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);
}
13
répondu anonymous 2016-08-15 15:31:13

Linux seulement

Réglage de simples variables d'environnement (basé sur la réponse par 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);
    }
}

Utilisation:

tout d'abord, mettez la méthode dans n'importe quelle classe que vous voulez, par exemple SystemUtil.

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

si vous appelez System.getenv("SHELL") après ça, vous récupérerez "/bin/bash" .

9
répondu Hubert Grzeskowiak 2017-09-26 03:42:49

il s'avère que la solution de @pushy/@anonymous/@Edward Campbell ne fonctionne pas sur Android parce que Android n'est pas vraiment Java. Plus précisément, Android n'a pas java.lang.ProcessEnvironment du tout. Mais il s'avère être plus facile dans Android, vous avez juste besoin de faire un appel JNI à POSIX 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;
}

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);
    }
}
7
répondu Hans-Christoph Steiner 2013-12-07 03:22:15

c'est une combinaison de la réponse de @paul-blair convertie en Java qui inclut quelques nettoyages pointés par paul blair et quelques erreurs qui semblent avoir été à l'intérieur du code de @pushy qui est composé de @Edward Campbell et anonymous.

Je ne peux pas souligner à quel point ce code ne doit être utilisé que dans les tests et est extrêmement hacky. 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.

Ce aussi inclut quelques touches mineures de la mienne qui permettent au code de fonctionner sur les deux fenêtres tournant sur

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 les Centos tournant 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));
}
6
répondu mangusbrother 2016-06-28 10:35:05

en fouillant sur internet, il semble qu'il soit possible de le faire avec JNI. Vous devrez alors faire un appel à putenv() à partir de C, et vous devrez (probablement) le faire d'une manière qui fonctionne à la fois sur Windows et UNIX.

si tout ce qui peut être fait, il ne serait sûrement pas trop difficile pour Java lui-même de soutenir cela au lieu de me mettre dans une veste droite.

un ami parlant Perl ailleurs suggère que c'est parce que les variables d'environnement sont processus global et Java s'efforce de bonne isolation pour une bonne conception.

3
répondu skiphoppy 2008-11-26 15:51:47

a essayé la réponse de pushy ci-dessus et cela a fonctionné pour la plupart. Toutefois, dans certaines circonstances, je verrais cette exception:

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

il s'avère que cela se produit lorsque la méthode a été appelée plus d'une fois, en raison de l'implémentation de certaines classes internes de ProcessEnvironment. si la méthode setEnv(..) est appelée plus d'une fois, lorsque les clés sont extraites de la carte theEnvironment , elles sont maintenant des chaînes (ayant été insérées en tant que chaînes par le premier l'invocation de la setEnv(...) ) et ne peuvent pas être jeté à la carte est de type générique, Variable, qui est un privé intérieur de la classe des ProcessEnvironment.

une version fixe (en Scala), est ci-dessous. J'espère que ce n'est pas trop difficile à transporter dans 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()
  }
}
2
répondu Paul Blair 2013-09-27 00:01:53

comme la plupart des gens qui ont trouvé ce fil, j'écrivais des tests unitaires et j'ai dû modifier les variables d'environnement pour définir les conditions correctes pour que le test fonctionne. Cependant, j'ai trouvé que les réponses les plus optimistes avaient des problèmes et/ou étaient très cryptiques ou trop compliquées. Espérons que cela aidera les autres à trouver la solution plus rapidement.

tout d'abord, j'ai finalement trouvé la solution de @Hubert Grzeskowiak la plus simple et cela a fonctionné pour moi. Je souhaite que je n'aurait que la première. Il est basé sur la réponse de @Edward Campbell, mais sans compliquer la recherche de boucle.

cependant, j'ai commencé avec la solution de @pushy, qui a obtenu le plus de voix positives. C'est un mélange de @anonymous et de @Edward Campbell. @pushy affirme que les deux approches sont nécessaires pour couvrir les environnements Linux et Windows. Je cours sous OS X et je trouve que les deux fonctionnent (une fois qu'un problème avec @anonymous approach est corrigé). Comme d'autres l'ont noté, ce la solution fonctionne la plupart du temps, mais pas tous.

je pense que la source de la plus grande partie de la confusion vient de la solution de @anonymous opérant dans le domaine de "l'environnement". Si l'on considère la définition de la structure Processenvironnement , "theenvironnement" n'est pas une carte< String, String > mais plutôt une carte< Variable, valeur >. Nettoyer la carte fonctionne bien, mais l'opération putAll reconstruit la carte une carte< String, String >, ce qui provoque potentiellement problèmes lorsque des opérations ultérieures fonctionnent sur la structure de données en utilisant L'API normale qui attend Map< Variable, Value >. De plus, l'accès et la suppression d'éléments individuels constituent un problème. La solution consiste à accéder "à l'environnement" indirectement par "l'environnement non modifiable". Mais comme il s'agit d'un type Unmodifiablmap , l'accès doit être effectué via la variable privée " m " du type Unmodifiablmap. Voir getModifiableEnvironmentMap2 dans le code ci-dessous.

dans mon cas, j'ai dû supprimer certaines variables d'environnement pour mon test (les autres devraient être inchangées). Ensuite, j'ai voulu restaurer les variables d'environnement à leur état précédent après le test. Les routines ci-dessous rendent cela simple à faire. J'ai testé les deux versions de getmodifiablenvironmentmap sur OS X, et les deux fonctionnent de manière équivalente. Bien que basé sur les commentaires dans ce fil, l'un peut être un meilleur choix que l'autre en fonction de l'environnement.

Note: je n'ai pas inclus l'accès au "domainecaseinsensiveenvironnemental" 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);
}
2
répondu Tim Ryan 2017-03-22 23:06:12

Kotlin mise en œuvre récemment, j'ai fait basé sur Edward réponse:

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)
    }
}
0
répondu Rik 2018-07-04 07:20:46

vous pouvez passer des paramètres dans votre processus java initial avec -D:

java -cp <classpath> -Dkey1=value -Dkey2=value ...
-6
répondu matt b 2008-11-25 17:42:09