Come posso impostare le variabili di ambiente da Java?


289

Come posso impostare le variabili di ambiente da Java? Vedo che posso farlo per i sottoprocessi utilizzando ProcessBuilder. Tuttavia, ho diversi sottoprocessi da avviare, quindi preferirei modificare l'ambiente del processo corrente e lasciare che i sottoprocessi lo ereditino.

C'è un System.getenv(String)per ottenere una singola variabile d'ambiente. Posso anche ottenere una Mapserie completa di variabili d'ambiente con System.getenv(). Ma invocare put()ciò Mapgenera un UnsupportedOperationException- apparentemente significano che l'ambiente è di sola lettura. E non c'è System.setenv().

Quindi, c'è un modo per impostare le variabili di ambiente nel processo attualmente in esecuzione? Se é cosi, come? In caso contrario, qual è la logica? (È perché questo è Java e quindi non dovrei fare cose obsolete malefiche e non instabili come toccare il mio ambiente?) E se no, qualche buon suggerimento per gestire i cambiamenti delle variabili di ambiente che dovrò alimentare con diversi sottoprocessi?


System.getEnv () è pensato per essere universale, alcuni ambienti non hanno nemmeno variabili d'ambiente.
b1nary.atr0phy,

7
Per chi aveva bisogno di questo per un caso d'uso di test di unità: stackoverflow.com/questions/8168884/...
Atifm

Risposte:


88

(È perché si tratta di Java e quindi non dovrei fare cose obsolete non portabili malvagie come toccare il mio ambiente?)

Penso che tu abbia colpito l'unghia sulla testa.

Un modo possibile per alleggerire l'onere sarebbe quello di scomporre un metodo

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

e passare qualsiasi ProcessBuilders prima di avviarli.

Inoltre, probabilmente lo sai già, ma puoi avviare più di un processo con lo stesso ProcessBuilder. Quindi, se i tuoi sottoprocessi sono gli stessi, non è necessario ripetere questa configurazione più volte.


1
È un peccato che la gestione non mi permetta di usare un linguaggio portatile diverso per eseguire questo insieme di sottoprocessi obsoleti e malvagi. :)
skiphoppy,

18
S. Lott, non sto cercando di impostare l'ambiente di un genitore. Sto cercando di impostare il mio ambiente.
skiphoppy,

3
Funziona benissimo, a meno che non sia la libreria di qualcun altro (es. Quella di Sun) a lanciare il processo.
Sullivan-

24
@ b1naryatr0phy Hai perso il punto. Nessuno può giocare con le variabili di ambiente poiché tali variabili sono locali in un processo (ciò che si imposta in Windows sono i valori predefiniti). Ogni processo è libero di cambiare le proprie variabili ... a meno che non sia Java.
maaartinus,

9
Questa limitazione di Java è un po 'fuori di testa. Non vi è alcun motivo per cui java non ti permetta di impostare env vars se non "perché non vogliamo che java faccia questo".
IanNorton,

232

Per l'utilizzo in scenari in cui è necessario impostare valori di ambiente specifici per unit test, è possibile che sia utile il seguente hack. Cambierà le variabili di ambiente in tutta la JVM (quindi assicurati di reimpostare eventuali modifiche dopo il test), ma non altererà l'ambiente di sistema.

Ho scoperto che una combinazione dei due hack sporchi di Edward Campbell e anonimo funziona meglio, poiché uno dei non funziona sotto Linux, uno non funziona sotto Windows 7. Quindi per ottenere un hack malevolo multipiattaforma li ho combinati:

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

Funziona come un fascino. Crediti completi per i due autori di questi hack.


1
Questo cambierà solo in memoria o cambierà effettivamente l'intera variabile d'ambiente nel sistema?
Shervin Asgari,

36
Questo cambierà solo la variabile d'ambiente in memoria. Questo è utile per i test, perché è possibile impostare la variabile d'ambiente come necessario per il test, ma lasciare gli env nel sistema così come sono. In effetti, scoraggerei fortemente chiunque dall'utilizzare questo codice per scopi diversi dai test. Questo codice è malvagio ;-)
invadente

9
Come FYI, JVM crea una copia delle variabili di ambiente all'avvio. Ciò modificherà quella copia, non le variabili di ambiente per il processo padre che ha avviato la JVM.
bmeding

Ho provato questo su Android e non mi sembra di prenderlo. Qualcun altro ha avuto fortuna su Android?
Hans-Christoph Steiner,

5
Certo,import java.lang.reflect.Field;
invadente il

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

O per aggiungere / aggiornare un singolo var e rimuovere il loop secondo il suggerimento di 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);
  }

3
Sembra che modificherebbe la mappa in memoria, ma salverebbe il valore sul sistema?
Jon Onstott,

1
bene cambia la mappa di memoria delle variabili d'ambiente. immagino che sia sufficiente in molti casi d'uso. @Edward - Accidenti, è difficile immaginare come questa soluzione sia stata concepita in primo luogo!
anirvan,

13
Questo non cambierà le variabili di ambiente sul sistema, ma le cambierà nell'attuale invocazione di Java. Questo è molto utile per i test unitari.
Stuart K,

10
perché non usare al Class<?> cl = env.getClass();posto di quello per loop?
thejoshwolfe,

1
Questo è esattamente quello che stavo cercando! Ho scritto test di integrazione per alcuni codici che utilizzano uno strumento di terze parti che, per qualche motivo, ti consente solo di modificare la sua lunghezza di timeout predefinita assurdamente breve con una variabile ambientale.
David DeMar,

21
// this is a dirty hack - but should be ok for a unittest.
private void setNewEnvironmentHack(Map<String, String> newenv) throws Exception
{
  Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
  Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
  theEnvironmentField.setAccessible(true);
  Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
  env.clear();
  env.putAll(newenv);
  Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
  theCaseInsensitiveEnvironmentField.setAccessible(true);
  Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
  cienv.clear();
  cienv.putAll(newenv);
}

17

su Android l'interfaccia è esposta tramite Libcore.os come una sorta di API nascosta.

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

La classe Libcore e il sistema operativo dell'interfaccia sono pubblici. Manca solo la dichiarazione di classe e deve essere mostrata al linker. Non è necessario aggiungere le classi all'applicazione, ma non fa male se è incluso.

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
Testato e funzionante su Android 4.4.4 (CM11). PS L'unica regolazione feci stava sostituendo throws ErrnoExceptioncon throws Exception.
DavisNT,

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

1
Potenzialmente defunto ora con le nuove restrizioni di Pie: developer.android.com/about/versions/pie/…
TWiStErRob

13

Solo Linux

Impostazione di variabili d'ambiente singole (basate sulla risposta di 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);
    }
}

Uso:

Innanzitutto, inserisci il metodo in qualsiasi classe desideri, ad esempio SystemUtil. Quindi chiamalo staticamente:

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

Se chiami System.getenv("SHELL")dopo questo, "/bin/bash"tornerai.


Quanto sopra non funziona in Windows 10, ma sarà lavorare in Linux.
mengchengfeng,

Interessante. Non l'ho provato io stesso su Windows. Ricevi un errore, @mengchengfeng?
Hubert Grzeskowiak,

@HubertGrzeskowiak Non abbiamo visto alcun messaggio di errore, semplicemente non ha funzionato ...
mengchengfeng,

9

Questa è una combinazione della risposta di @ paul-blair convertita in Java che include alcune correzioni evidenziate da Paul Blair e alcuni errori che sembrano essere stati nel codice di @pushy che è composto da @Edward Campbell e anonimo.

Non posso sottolineare quanto questo codice debba essere utilizzato SOLO nei test ed è estremamente confuso. Ma per i casi in cui è necessaria la configurazione dell'ambiente nei test è esattamente ciò di cui avevo bisogno.

Ciò include anche alcuni miei piccoli tocchi che consentono al codice di funzionare su entrambi i Windows in esecuzione

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)

così come Centos in esecuzione

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)

L'implemento:

/**
 * Sets an environment variable FOR THE CURRENT RUN OF THE JVM
 * Does not actually modify the system's environment variables,
 *  but rather only the copy of the variables that java has taken,
 *  and hence should only be used for testing purposes!
 * @param key The Name of the variable to set
 * @param value The value of the variable to set
 */
@SuppressWarnings("unchecked")
public static <K,V> void setenv(final String key, final String value) {
    try {
        /// we obtain the actual environment
        final Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
        final Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
        final boolean environmentAccessibility = theEnvironmentField.isAccessible();
        theEnvironmentField.setAccessible(true);

        final Map<K,V> env = (Map<K, V>) theEnvironmentField.get(null);

        if (SystemUtils.IS_OS_WINDOWS) {
            // This is all that is needed on windows running java jdk 1.8.0_92
            if (value == null) {
                env.remove(key);
            } else {
                env.put((K) key, (V) value);
            }
        } else {
            // This is triggered to work on openjdk 1.8.0_91
            // The ProcessEnvironment$Variable is the key of the map
            final Class<K> variableClass = (Class<K>) Class.forName("java.lang.ProcessEnvironment$Variable");
            final Method convertToVariable = variableClass.getMethod("valueOf", String.class);
            final boolean conversionVariableAccessibility = convertToVariable.isAccessible();
            convertToVariable.setAccessible(true);

            // The ProcessEnvironment$Value is the value fo the map
            final Class<V> valueClass = (Class<V>) Class.forName("java.lang.ProcessEnvironment$Value");
            final Method convertToValue = valueClass.getMethod("valueOf", String.class);
            final boolean conversionValueAccessibility = convertToValue.isAccessible();
            convertToValue.setAccessible(true);

            if (value == null) {
                env.remove(convertToVariable.invoke(null, key));
            } else {
                // we place the new value inside the map after conversion so as to
                // avoid class cast exceptions when rerunning this code
                env.put((K) convertToVariable.invoke(null, key), (V) convertToValue.invoke(null, value));

                // reset accessibility to what they were
                convertToValue.setAccessible(conversionValueAccessibility);
                convertToVariable.setAccessible(conversionVariableAccessibility);
            }
        }
        // reset environment accessibility
        theEnvironmentField.setAccessible(environmentAccessibility);

        // we apply the same to the case insensitive environment
        final Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
        final boolean insensitiveAccessibility = theCaseInsensitiveEnvironmentField.isAccessible();
        theCaseInsensitiveEnvironmentField.setAccessible(true);
        // Not entirely sure if this needs to be casted to ProcessEnvironment$Variable and $Value as well
        final Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
        if (value == null) {
            // remove if null
            cienv.remove(key);
        } else {
            cienv.put(key, value);
        }
        theCaseInsensitiveEnvironmentField.setAccessible(insensitiveAccessibility);
    } catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+">", e);
    } catch (final NoSuchFieldException e) {
        // we could not find theEnvironment
        final Map<String, String> env = System.getenv();
        Stream.of(Collections.class.getDeclaredClasses())
                // obtain the declared classes of type $UnmodifiableMap
                .filter(c1 -> "java.util.Collections$UnmodifiableMap".equals(c1.getName()))
                .map(c1 -> {
                    try {
                        return c1.getDeclaredField("m");
                    } catch (final NoSuchFieldException e1) {
                        throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+"> when locating in-class memory map of environment", e1);
                    }
                })
                .forEach(field -> {
                    try {
                        final boolean fieldAccessibility = field.isAccessible();
                        field.setAccessible(true);
                        // we obtain the environment
                        final Map<String, String> map = (Map<String, String>) field.get(env);
                        if (value == null) {
                            // remove if null
                            map.remove(key);
                        } else {
                            map.put(key, value);
                        }
                        // reset accessibility
                        field.setAccessible(fieldAccessibility);
                    } catch (final ConcurrentModificationException e1) {
                        // This may happen if we keep backups of the environment before calling this method
                        // as the map that we kept as a backup may be picked up inside this block.
                        // So we simply skip this attempt and continue adjusting the other maps
                        // To avoid this one should always keep individual keys/value backups not the entire map
                        LOGGER.info("Attempted to modify source map: "+field.getDeclaringClass()+"#"+field.getName(), e1);
                    } catch (final IllegalAccessException e1) {
                        throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+">. Unable to access field!", e1);
                    }
                });
    }
    LOGGER.info("Set environment variable <"+key+"> to <"+value+">. Sanity Check: "+System.getenv(key));
}

7

Si scopre che la soluzione di @ pushy / @ anonymous / @ Edward Campbell non funziona su Android perché Android non è proprio Java. In particolare, Android non ha java.lang.ProcessEnvironmentaffatto. Ma risulta essere più facile in Android, devi solo fare una chiamata JNI a 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;
}

E in Java:

public class Posix {

    public static native int setenv(String key, String value, boolean overwrite);

    private void runTest() {
        Posix.setenv("LD_LIBRARY_PATH", "foo", true);
    }
}

5

Come la maggior parte delle persone che hanno trovato questo thread, stavo scrivendo alcuni test unitari e dovevo modificare le variabili di ambiente per impostare le condizioni corrette per l'esecuzione del test. Tuttavia, ho scoperto che le risposte più votate avevano alcuni problemi e / o erano molto criptiche o eccessivamente complicate. Speriamo che questo possa aiutare gli altri a risolvere la soluzione più rapidamente.

Prima di tutto, ho finalmente trovato la soluzione di @Hubert Grzeskowiak per essere la più semplice e ha funzionato per me. Vorrei essere venuto prima a quello. Si basa sulla risposta di @Edward Campbell, ma senza complicare la ricerca in loop.

Tuttavia, ho iniziato con la soluzione di @ pushy, che ha ottenuto il maggior numero di voti. È una combinazione di @anonymous e @Edward Campbell. @pushy afferma che entrambi gli approcci sono necessari per coprire ambienti Linux e Windows. Sono in esecuzione su OS X e trovo che entrambi funzionano (una volta risolto un problema con l'approccio @anonymous). Come altri hanno notato, questa soluzione funziona la maggior parte delle volte, ma non tutto.

Penso che la fonte della maggior parte della confusione derivi dalla soluzione di @ anonymous operante nel campo "theEnvironment". Guardando la definizione della struttura ProcessEnvironment , 'theEnvironment' non è una mappa <String, String> ma piuttosto una mappa <Variabile, Valore>. L'eliminazione della mappa funziona correttamente, ma l'operazione putAll ricostruisce la mappa una <stringa, stringa>, che potenzialmente causa problemi quando le operazioni successive operano sulla struttura dei dati utilizzando l'API normale che prevede la mappa <variabile, valore>. Inoltre, l'accesso / rimozione di singoli elementi è un problema. La soluzione è accedere indirettamente all '"Ambiente" tramite "Ambiente immodificabile". Ma poiché questo è un tipo UnmodifiableMapl'accesso deve essere effettuato tramite la variabile privata 'm' del tipo UnmodifiableMap. Vedi getModifiableEnvironmentMap2 nel codice seguente.

Nel mio caso ho dovuto rimuovere alcune delle variabili d'ambiente per il mio test (le altre dovrebbero essere invariate). Quindi ho voluto ripristinare le variabili di ambiente al loro stato precedente dopo il test. Le routine sottostanti lo rendono semplice. Ho testato entrambe le versioni di getModifiableEnvironmentMap su OS X ed entrambe funzionano in modo equivalente. Sebbene basato sui commenti in questo thread, uno potrebbe essere una scelta migliore dell'altro a seconda dell'ambiente.

Nota: non ho incluso l'accesso a "theCaseInsensitiveEnvironmentField" poiché sembra essere specifico di Windows e non ho avuto modo di testarlo, ma aggiungerlo dovrebbe essere semplice.

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

Grazie, era esattamente il mio caso d'uso e anche sotto mac os x.
Rafael Gonçalves,

Mi è piaciuto così tanto che ho trovato una versione leggermente più semplice per Groovy, vedi sotto.
mike rodent,

4

Cercando online, sembra che sia possibile farlo con JNI. Dovresti quindi effettuare una chiamata a putenv () da C e (presumibilmente) dovresti farlo in un modo che funzionasse sia su Windows che su UNIX.

Se tutto ciò può essere fatto, sicuramente non sarebbe troppo difficile per Java stesso supportare questo invece di mettermi in una giacca dritta.

Un amico che parla Perl altrove suggerisce che ciò è dovuto al fatto che le variabili di ambiente sono globali di processo e Java sta cercando un buon isolamento per una buona progettazione.


Sì, è possibile impostare l'ambiente dei processi dal codice C. Ma non contare su quello che funziona in Java. È possibile che JVM copi l'ambiente in oggetti String Java durante l'avvio, pertanto le modifiche non verranno utilizzate per future operazioni JVM.
Darron,

Grazie per l'avvertimento, Darron. Probabilmente ci sono buone probabilità che tu abbia ragione.
skiphoppy,

2
@Darron molte delle ragioni per cui si vorrebbe fare questo non hanno nulla a che fare con ciò che la JVM pensa sia l'ambiente. (Pensa alle impostazioni LD_LIBRARY_PATHprima di chiamare Runtime.loadLibrary(); la dlopen()chiamata che invoca guarda l' ambiente reale , non l'idea di Java dello stesso).
Charles Duffy,

Questo funziona per i sottoprocessi avviati da una libreria nativa (che nel mio caso è la maggior parte di essi), ma sfortunatamente non funziona per i sottoprocessi avviati dalle classi Process o ProcessBuilder di Java.
Dan

4

Ho provato la risposta di Pushy sopra e ha funzionato per la maggior parte. Tuttavia, in determinate circostanze, vedrei questa eccezione:

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

Ciò si verifica quando il metodo è stato chiamato più di una volta, a causa dell'implementazione di alcune classi interne di ProcessEnvironment.Se il setEnv(..)metodo viene chiamato più di una volta, quando le chiavi vengono recuperate dalla theEnvironmentmappa, ora sono stringhe (essendo state inserite come stringhe dalla prima invocazione di setEnv(...)) e non può essere trasmesso al tipo generico della mappa, Variable,che è una classe interna privata diProcessEnvironment.

Una versione fissa (in Scala), è sotto. Spero che non sia troppo difficile trasferirsi in 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()
  }
}

Dove viene definito JavaClass?
Mike Slinn,

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

1
L'implementazione di java.lang.ProcessEnvironment è diversa su piattaforme diverse anche per la stessa build. Ad esempio, non esiste una classe java.lang.ProcessEnvironment $ Variabile nell'implementazione di Windows ma questa classe esiste in una per Linux. Puoi controllarlo facilmente. Basta scaricare tar.gz JDK distribution per Linux ed estrarre il sorgente da src.zip quindi confrontarlo con lo stesso file dalla distribuzione per Windows. Sono totalmente diversi in JDK 1.8.0_181. Non li ho controllati in Java 10 ma non sarò sorpreso se c'è la stessa immagine.
Alex Konshin,

1

Questa è la versione malvagia di Kotlin della risposta malvagia di @ pushy =)

@Suppress("UNCHECKED_CAST")
@Throws(Exception::class)
fun setEnv(newenv: Map<String, String>) {
    try {
        val processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment")
        val theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment")
        theEnvironmentField.isAccessible = true
        val env = theEnvironmentField.get(null) as MutableMap<String, String>
        env.putAll(newenv)
        val theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment")
        theCaseInsensitiveEnvironmentField.isAccessible = true
        val cienv = theCaseInsensitiveEnvironmentField.get(null) as MutableMap<String, String>
        cienv.putAll(newenv)
    } catch (e: NoSuchFieldException) {
        val classes = Collections::class.java.getDeclaredClasses()
        val env = System.getenv()
        for (cl in classes) {
            if ("java.util.Collections\$UnmodifiableMap" == cl.getName()) {
                val field = cl.getDeclaredField("m")
                field.setAccessible(true)
                val obj = field.get(env)
                val map = obj as MutableMap<String, String>
                map.clear()
                map.putAll(newenv)
            }
        }
    }

Funziona almeno in macOS Mojave.


0

Se lavori con SpringBoot puoi aggiungere specificando la variabile ambientale nella seguente proprietà:

was.app.config.properties.toSystemProperties

1
Puoi per favore spiegare un po '?
Faraz,

0

variante basata sulla risposta di @ pushy , funziona su 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)

Uso:

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

0

La risposta di Tim Ryan ha funzionato per me ... ma la volevo per Groovy (ad esempio il contesto Spock), e semplicissimo:

import java.lang.reflect.Field

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

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

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

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

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

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

0

Una versione di Kotlin, in questo algoritmo ho creato un decoratore che ti consente di impostare e ottenere variabili dall'ambiente.

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

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

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

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

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

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

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

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

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

-1

Implementazione di Kotlin che ho fatto di recente sulla base della risposta di 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)
    }
}

-12

Puoi passare i parametri nel tuo processo java iniziale con -D:

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

I valori non sono noti al momento dell'esecuzione; diventano noti durante l'esecuzione del programma quando l'utente li fornisce / li seleziona. E questo imposta solo le proprietà di sistema, non le variabili di ambiente.
skiphoppy,

Quindi in quel caso probabilmente vorrai trovare un modo regolare (tramite il parametro args [] al metodo principale) per invocare i tuoi sottoprocessi.
matt b

opaco b, il modo normale è tramite ProcessBuilder, come menzionato nella mia domanda originale. :)
skiphoppy

7
I parametri -D sono disponibili tramite System.getPropertye non sono gli stessi di System.getenv. Inoltre, la Systemclasse permette anche di impostare staticamente queste proprietà usandosetProperty
anirvan il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.