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

289

Comment définir des variables d'environnement à partir de Java? Je vois que je peux le faire pour les sous-processus utilisant ProcessBuilder. J'ai plusieurs sous-processus à démarrer, cependant, je préfère donc 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 un Mapensemble complet de variables d'environnement avec System.getenv(). Mais, invoquer put()cela Mapjette un UnsupportedOperationException- apparemment, cela signifie que l'environnement est en lecture seule. Et, il n'y a pas System.setenv().

Alors, existe-t-il un moyen de définir des variables d'environnement dans le processus en cours d'exécution? Si c'est le cas, comment? Sinon, quelle est la justification? (Est-ce parce que c'est Java et que je ne devrais donc pas faire de mauvaises choses obsolètes non portables comme toucher mon environnement?) Et sinon, de bonnes suggestions pour gérer les changements de variables d'environnement que je vais devoir alimenter à plusieurs sous-processus?

skiphoppy
la source
System.getEnv () est destiné à être universel, certains environnements n'ont même pas de variables d'environnement.
b1nary.atr0phy
7
Pour tous ceux qui en ont besoin pour un cas d'utilisation de test unitaire: stackoverflow.com/questions/8168884/…
Atifm
Pour Scala, utilisez ceci: gist.github.com/vpatryshev/b1bbd15e2b759c157b58b68c58891ff4
Vlad Patryshev

Réponses:

88

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

Je pense que vous avez mis le doigt sur la tête.

Un moyen possible d'alléger la charge consisterait à éliminer une méthode

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

et passer tout ProcessBuilder avant de les démarrer.

De plus, vous le savez probablement déjà, mais vous pouvez démarrer plusieurs processus avec le même ProcessBuilder. Donc, si vos sous-processus sont les mêmes, vous n'avez pas besoin de refaire cette configuration encore et encore.

Michael Myers
la source
1
C'est une honte que la gestion ne me permette pas d'utiliser un langage portable différent pour exécuter cet ensemble de sous-processus diaboliques et obsolètes. :)
skiphoppy
18
S.Lott, je ne cherche pas à définir l'environnement d'un parent. Je cherche à définir mon propre environnement.
skiphoppy
3
Cela fonctionne très bien, à moins que ce ne soit la bibliothèque de quelqu'un d'autre (par exemple celle de Sun) qui lance le processus.
sullivan-
24
@ b1naryatr0phy Vous avez raté le point. Personne ne peut jouer avec vos variables d'environnement car ces variables sont locales à un processus (ce que vous définissez dans Windows sont les valeurs par défaut). Chaque processus est libre de changer ses propres variables ... sauf son Java.
maaartinus
9
Cette limitation de java est un peu un flic. Il n'y a aucune raison pour que java ne vous permette pas de définir des vars env autres que "parce que nous ne voulons pas que java le fasse".
IanNorton
232

Pour une utilisation dans des scénarios où vous devez définir des valeurs d'environnement spécifiques pour les tests unitaires, vous pouvez trouver le hack suivant utile. Il modifiera les variables d'environnement dans la JVM (assurez-vous donc de réinitialiser toutes les modifications après votre test), mais ne modifiera pas votre environnement système.

J'ai trouvé qu'une combinaison des deux hacks sales d'Edward Campbell et anonyme fonctionne mieux, car l'un ne fonctionne pas sous Linux, l'autre ne fonctionne pas sous Windows 7. Donc, pour obtenir un hack diabolique multiplateforme, 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. Remerciements complets aux deux auteurs de ces hacks.

arrogant
la source
1
Cela ne changera-t-il que dans la mémoire, ou changera-t-il réellement toute la variable d'environnement dans le système?
Shervin Asgari
36
Cela ne changera que la variable d'environnement en mémoire. C'est bon pour les tests, car vous pouvez définir la variable d'environnement comme nécessaire pour votre test, mais laissez les envs dans le système tels quels. En fait, je découragerais fortement quiconque d'utiliser ce code à d'autres fins que les tests. Ce code est diabolique ;-)
pushy
9
En tant que FYI, la JVM crée une copie des variables d'environnement au démarrage. Cela modifiera cette copie, pas les variables d'environnement pour le processus parent qui a démarré la JVM.
bmeding
J'ai essayé cela sur Android et cela n'a pas semblé durer. Quelqu'un d'autre a-t-il de la chance sur Android?
Hans-Christoph Steiner
5
Bien sûr,import java.lang.reflect.Field;
arrogant
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);
        }
    }
}

Ou pour ajouter / mettre à jour une seule var et supprimer la boucle selon la suggestion de thejoshwolfe.

@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);
  }
Kashyap
la source
3
Il semblerait que cela modifierait la carte en mémoire, mais enregistrerait-il la valeur dans le système?
Jon Onstott
1
cela change la carte mémoire des variables d'environnement. je suppose que cela suffit dans beaucoup de cas d'utilisation. @Edward - mon Dieu, il est difficile d'imaginer comment cette solution a été trouvée en premier lieu!
anirvan
13
Cela ne changera pas les variables d'environnement sur le système, mais les changera dans l'invocation actuelle de Java. Ceci est très utile pour les tests unitaires.
Stuart K
10
pourquoi ne pas utiliser à la Class<?> cl = env.getClass();place de cela pour la boucle?
thejoshwolfe
1
C'est exactement ce que je cherchais! J'ai écrit des tests d'intégration pour du code qui utilise un outil tiers qui, pour une raison quelconque, ne vous permet de modifier sa durée de temporisation par défaut absurdement courte avec une variable d'environnement.
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);
}
anonyme
la source
17

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. Juste la déclaration de classe est manquante et doit être montrée à l'éditeur de liens. Pas besoin d'ajouter les classes à l'application, mais cela ne fait pas de mal 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;
}
user3404318
la source
1
Testé et fonctionnant sur Android 4.4.4 (CM11). PS Le seul ajustement I fait remplaçait throws ErrnoExceptionavec throws Exception.
DavisNT
7
API 21, a Os.setEnvmaintenant. developer.android.com/reference/android/system/… , java.lang.String, boolean)
Jared Burrows
1
Potentiellement disparu maintenant avec les nouvelles restrictions de Pie: developer.android.com/about/versions/pie/…
TWiStErRob
13

Linux uniquement

Définition de variables d'environnement uniques (d'après la réponse d'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);
    }
}

Usage:

Tout d'abord, placez la méthode dans la classe de votre choix, par exemple SystemUtil. Appelez-le ensuite statiquement:

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

Si vous appelez System.getenv("SHELL")après cela, vous reviendrez "/bin/bash".

Hubert Grzeskowiak
la source
Le ci - dessus ne fonctionne pas dans Windows 10, mais va travailler sous Linux.
mengchengfeng
Intéressant. Je ne l'ai pas essayé moi-même sous Windows. Vous obtenez une erreur, @mengchengfeng?
Hubert Grzeskowiak
@HubertGrzeskowiak Nous n'avons vu aucun message d'erreur, cela n'a tout simplement pas fonctionné ...
mengchengfeng
9

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

Je ne peux pas souligner à quel point ce code doit être utilisé UNIQUEMENT 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.

Cela inclut également quelques touches mineures qui permettent au code de fonctionner sur les deux Windows fonctionnant 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 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 oeuvre:

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

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'en a pas java.lang.ProcessEnvironmentdu tout. Mais cela s'avère plus facile sous 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);
    }
}
Hans-Christoph Steiner
la source
5

Comme la plupart des gens qui ont trouvé ce fil, j'écrivais des tests unitaires et je devais modifier les variables d'environnement pour définir les conditions correctes pour l'exécution du test. Cependant, j'ai trouvé que les réponses les plus votées avaient des problèmes et / ou étaient très cryptiques ou trop compliquées. J'espère que cela aidera les autres à trier 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. J'aurais aimé y arriver en premier. 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 votes positifs. Il s'agit d'un combo de @anonymous et @Edward Campbell's. @pushy affirme que les deux approches sont nécessaires pour couvrir les environnements Linux et Windows. Je suis sous OS X et je trouve que les deux fonctionnent (une fois qu'un problème avec l'approche @anonymous est résolu). Comme d'autres l'ont noté, cette solution fonctionne la plupart du temps, mais pas tous.

Je pense que la source de la plupart des confusions vient de la solution de @ anonymous opérant sur le champ 'theEnvironment'. En regardant la définition de la structure ProcessEnvironment , 'theEnvironment' n'est pas une Map <String, String> mais plutôt une Map <Variable, Value>. L'effacement de la carte fonctionne correctement, mais l'opération putAll reconstruit la carte en Map <String, String>, ce qui peut potentiellement poser des problèmes lorsque des opérations ultérieures opèrent sur la structure de données à l'aide de l'API normale qui attend Map <Variable, Value>. De plus, l'accès / la suppression d'éléments individuels est un problème. La solution consiste à accéder indirectement à «l'environnement» via «l'environnement non modifiable». Mais puisque c'est un type UnmodifiableMapl'accès doit se faire via la variable privée 'm' de type UnmodifiableMap. Voir getModifiableEnvironmentMap2 dans le code ci-dessous.

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

Remarque: Je n'ai pas inclus l'accès au '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);
}
Tim Ryan
la source
Merci, c'était exactement mon cas d'utilisation et sous mac os x aussi.
Rafael Gonçalves
J'ai tellement aimé cela que j'ai créé une version légèrement plus simple pour Groovy, voir ci-dessous.
mike rodent
4

En fouillant en ligne, il semble qu'il soit possible de le faire avec JNI. Vous devrez alors appeler Poutenv () depuis C, et vous devrez (vraisemblablement) le faire d'une manière qui fonctionne à la fois sur Windows et UNIX.

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

Un ami parlant Perl ailleurs suggère que cela est dû au fait que les variables d'environnement sont globales au processus et que Java s'efforce d'obtenir une bonne isolation pour une bonne conception.

skiphoppy
la source
Oui, vous pouvez définir l'environnement des processus à partir du code C. Mais je ne compterais pas sur ça pour travailler en Java. Il y a de fortes chances que la JVM copie l'environnement dans des objets Java String au démarrage, de sorte que vos modifications ne seront pas utilisées pour de futures opérations JVM.
Darron
Merci pour l'avertissement, Darron. Il y a probablement de bonnes chances que vous ayez raison.
skiphoppy
2
@Darron, de nombreuses raisons pour lesquelles on voudrait faire cela n'ont rien à voir avec ce que la JVM pense de l'environnement. (Pensez à définir LD_LIBRARY_PATHavant d'appeler Runtime.loadLibrary(); l' dlopen()appel qu'il invoque regarde l' environnement réel , pas l'idée de Java de la même chose).
Charles Duffy
Cela fonctionne pour les sous-processus démarrés par une bibliothèque native (qui dans mon cas est la plupart d'entre eux), mais ne fonctionne malheureusement pas pour les sous-processus démarrés par les classes Process ou ProcessBuilder de Java.
Dan
4

J'ai essayé la réponse de Pushy ci-dessus et cela a fonctionné pour la plupart. Cependant, dans certaines circonstances, je verrais cette exception:

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

Cela se produit lorsque la méthode a été appelée plusieurs fois, en raison de la mise en œuvre de certaines classes internes de ProcessEnvironment.Si la setEnv(..)méthode est appelée plusieurs fois, lorsque les clés sont récupérées de la theEnvironmentcarte, elles sont désormais des chaînes (ayant été insérées dans en tant que chaînes par la première invocation de setEnv(...)) et ne peut pas être converti en type générique de la carte, Variable,qui est une classe interne privée deProcessEnvironment.

Une version fixe (en Scala), est ci-dessous. Espérons que ce ne soit pas trop difficile à transférer 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()
  }
}
Paul Blair
la source
Où JavaClass est-il défini?
Mike Slinn
1
Vraisemblablement import java.lang.{Class => JavaClass}.
Randall Whitman
1
L'implémentation de java.lang.ProcessEnvironment est différente sur différentes plates-formes, même pour la même version. Par exemple, il n'y a pas de classe java.lang.ProcessEnvironment $ Variable dans l'implémentation de Windows mais cette classe existe en une seule pour Linux. Vous pouvez facilement le vérifier. Téléchargez simplement la distribution JDK tar.gz pour Linux et extrayez la source de src.zip, puis comparez-la avec le même fichier de la distribution pour Windows. Ils sont totalement différents dans JDK 1.8.0_181. Je ne les ai pas vérifiés en Java 10 mais je ne serai pas surpris s'il y a la même image.
Alex Konshin
1

Ceci est la version maléfique de Kotlin du mal de @ pushy réponse de =)

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

Il fonctionne au moins sur macOS Mojave.

GarouDan
la source
0

Si vous travaillez avec SpringBoot, vous pouvez ajouter en spécifiant la variable d'environnement dans la propriété suivante:

was.app.config.properties.toSystemProperties
Alex
la source
1
Pouvez-vous expliquer un peu?
Faraz
0

variante basée sur la réponse de @ pushy , fonctionne sur 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)

Usage:

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

La réponse de Tim Ryan a fonctionné pour moi ... mais je le voulais pour Groovy (contexte Spock par exemple), et 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|"
Mike rongeur
la source
0

Une version dans Kotlin, dans cet algorithme, j'ai créé un décorateur qui vous permet de définir et d'obtenir des variables de l'environnement.

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}")
}
Tiarê Balbi
la source
-1

Implémentation de Kotlin que j'ai récemment faite sur la base de la réponse d'Edward:

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)
    }
}
Rik
la source
-12

Vous pouvez passer des paramètres dans votre processus Java initial avec -D:

java -cp <classpath> -Dkey1=value -Dkey2=value ...
mat b
la source
Les valeurs ne sont pas connues au moment de l'exécution; ils deviennent connus lors de l'exécution du programme lorsque l'utilisateur les fournit / les sélectionne. Et cela ne définit que les propriétés du système, pas les variables d'environnement.
skiphoppy
Dans ce cas, vous voudrez probablement trouver un moyen régulier (via le paramètre args [] de la méthode principale) pour appeler vos sous-processus.
mat
matt b, la manière habituelle est via ProcessBuilder, comme mentionné dans ma question d'origine. :)
skiphoppy
7
Les paramètres -D sont disponibles via System.getPropertyet ne sont pas les mêmes que System.getenv. En outre, la Systemclasse permet également de définir ces propriétés statiquement à l'aide desetProperty
anirvan