Jak ustawić zmienne środowiskowe z Java?

289

Jak ustawić zmienne środowiskowe z Java? Widzę, że mogę to zrobić dla podprocesów za pomocą ProcessBuilder. Muszę jednak rozpocząć kilka podprocesów, więc wolę zmodyfikować środowisko bieżącego procesu i pozwolić, aby podprocesy go odziedziczyły.

Istnieje System.getenv(String)możliwość uzyskania pojedynczej zmiennej środowiskowej. Mogę również uzyskać Mappełny zestaw zmiennych środowiskowych za pomocą System.getenv(). Ale przywołanie put()tego Maprzuca UnsupportedOperationException- najwyraźniej oznacza to, że środowisko może być tylko do odczytu. I nie ma System.setenv().

Czy istnieje jakiś sposób na ustawienie zmiennych środowiskowych w aktualnie uruchomionym procesie? Jeśli tak to jak? Jeśli nie, jakie jest uzasadnienie? (Czy to dlatego, że jest to Java i dlatego nie powinienem robić złych, nieprzenoszalnych, przestarzałych rzeczy, takich jak dotykanie mojego środowiska?) A jeśli nie, jakieś dobre sugestie dotyczące zarządzania zmianami zmiennych środowiskowych, które będę musiał przekazać kilku podprocesy?

skiphoppy
źródło
System.getEnv () ma być uniwersalny, niektóre środowiska nawet nie mają zmiennych środowiskowych.
b1nary.atr0phy
7
Dla każdego, kto potrzebne to dla przypadku użycia testów jednostkowych: stackoverflow.com/questions/8168884/...
Atifm
W przypadku Scali użyj tego: gist.github.com/vpatryshev/b1bbd15e2b759c157b58b68c58891ff4
Vlad Patryshev

Odpowiedzi:

88

(Czy to dlatego, że jest to Java i dlatego nie powinienem robić złych, nieprzenoszalnych, przestarzałych rzeczy, takich jak dotykanie mojego środowiska?)

Myślę, że trafiłeś w sedno.

Możliwym sposobem na zmniejszenie obciążenia byłoby rozłożenie metody

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

i przejść ProcessBuilderprzez to s przed ich uruchomieniem.

Prawdopodobnie już o tym wiesz, ale możesz rozpocząć więcej niż jeden proces z tym samym ProcessBuilder. Więc jeśli twoje podprocesy są takie same, nie musisz wykonywać tej konfiguracji w kółko.

Michael Myers
źródło
1
Zarządzanie wstydem nie pozwoli mi używać innego przenośnego języka do uruchamiania tego zestawu złych, przestarzałych podprocesów. :)
skiphoppy
18
S.Lott, nie chcę ustawiać środowiska rodzica. Chcę ustawić własne środowisko.
skiphoppy
3
Działa to świetnie, chyba że inicjuje ten proces biblioteka innej osoby (np. Sun).
sullivan-
24
@ b1naryatr0phy Nie trafiłeś w sedno. Nikt nie może bawić się zmiennymi środowiskowymi, ponieważ zmienne te są lokalne dla procesu (ustawienia domyślne w systemie Windows). Każdy proces może dowolnie zmieniać własne zmienne ... chyba że jego Java.
maaartinus
9
To ograniczenie java jest trochę jak glina. Nie ma powodu, dla którego java nie pozwala ci ustawiać zmiennych env inaczej niż „ponieważ nie chcemy, aby java to robiła”.
IanNorton
232

Do użytku w scenariuszach, w których należy ustawić określone wartości środowiska dla testów jednostkowych, może okazać się przydatny następujący hack. Zmieni on zmienne środowiskowe w całej JVM (więc pamiętaj, aby zresetować wszelkie zmiany po teście), ale nie zmieni środowiska systemowego.

Odkryłem, że kombinacja dwóch brudnych hacków Edwarda Campbella i anonimowego działa najlepiej, ponieważ jeden z nich nie działa w systemie Linux, jeden nie działa pod Windows 7. Więc aby uzyskać wieloplatformowy hack zła, połączyłem je:

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

To działa jak urok. Pełne podziękowania dla dwóch autorów tych hacków.

natrętny
źródło
1
Czy to zmieni się tylko w pamięci, czy faktycznie zmieni całą zmienną środowiskową w systemie?
Shervin Asgari,
36
Spowoduje to jedynie zmianę zmiennej środowiskowej w pamięci. Jest to przydatne do testowania, ponieważ możesz ustawić zmienną środowiskową zgodnie z wymaganiami testu, ale pozostaw envs w systemie w niezmienionej postaci. W rzeczywistości zdecydowanie odradzałbym każdemu korzystanie z tego kodu do jakichkolwiek innych celów niż testowanie. Ten kod jest zły ;-)
natarczywy
9
Jako FYI JVM tworzy kopię zmiennych środowiskowych podczas uruchamiania. Spowoduje to edycję tej kopii, a nie zmiennych środowiskowych dla procesu nadrzędnego, który uruchomił JVM.
bmeding
Wypróbowałem to na Androidzie i nie wydawało się, że muszę. Czy ktoś jeszcze ma szczęście na Androidzie?
Hans-Christoph Steiner
5
Jasne,import java.lang.reflect.Field;
nachalny
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);
        }
    }
}

Lub dodać / zaktualizować pojedynczy var i usunąć pętlę zgodnie z sugestią Joshwolfe.

@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
źródło
3
Wygląda na to, że zmodyfikowałoby to mapę w pamięci, ale czy zapisałoby wartość w systemie?
Jon Onstott,
1
cóż, zmienia mapę pamięci zmiennych środowiskowych. myślę, że to wystarcza w wielu przypadkach użycia. @Edward - rany, trudno sobie wyobrazić, jak wymyślono to rozwiązanie!
anirvan
13
Nie zmieni to zmiennych środowiskowych w systemie, ale zmieni je w bieżącym wywołaniu Java. Jest to bardzo przydatne do testów jednostkowych.
Stuart K
10
dlaczego nie użyć Class<?> cl = env.getClass();zamiast tego dla pętli?
thejoshwolfe
1
Właśnie tego szukałem! Piszę testy integracyjne dla jakiegoś kodu, który używa narzędzia innej firmy, które z jakiegoś powodu pozwala jedynie modyfikować jego absurdalnie krótki domyślny limit czasu za pomocą zmiennej środowiskowej.
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);
}
anonimowy
źródło
17

na Androidzie interfejs jest udostępniany przez Libcore.os jako rodzaj ukrytego API.

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

Klasa Libcore oraz system operacyjny interfejsu są publiczne. Brakuje tylko deklaracji klasy i należy ją pokazać konsolidatorowi. Nie trzeba dodawać klas do aplikacji, ale nie zaszkodzi, jeśli zostanie uwzględniona.

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;
}
użytkownik3404318
źródło
1
Testowane i działające na Androidzie 4.4.4 (CM11). PS Jedyna regulacja zrobiłem było zastąpienie throws ErrnoExceptionz throws Exception.
DavisNT,
7
API 21 ma Os.setEnvteraz. developer.android.com/reference/android/system/… , java.lang.String, boolean)
Jared Burrows
1
Potencjalnie nieczynny teraz z nowymi ograniczeniami Pie: developer.android.com/about/versions/pie/…
TWiStErRob
13

Tylko Linux

Ustawianie pojedynczych zmiennych środowiskowych (na podstawie odpowiedzi Edwarda Campbella):

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

Stosowanie:

Najpierw umieść metodę w dowolnej klasie, np. SystemUtil. Następnie nazwij to statycznie:

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

Jeśli zadzwonisz System.getenv("SHELL")po tym, "/bin/bash"wrócisz.

Hubert Grzeskowiak
źródło
Powyższe nie działa w systemie Windows 10, ale będzie działać w systemie Linux.
mengchengfeng
Ciekawy. Nie próbowałem tego sam w systemie Windows. Czy pojawia się błąd, @mengchengfeng?
Hubert Grzeskowiak
@HubertGrzeskowiak Nie widzieliśmy żadnych komunikatów o błędach, po prostu nie działało ...
mengchengfeng
9

Jest to kombinacja odpowiedzi @ paul-blair przekonwertowanej na Javę, która obejmuje kilka porządków wskazanych przez Paula Blair oraz błędy, które wydają się znajdować w kodzie @pushy, który składa się z @Edward Campbell i jest anonimowy.

Nie mogę podkreślić, jak bardzo ten kod powinien być użyty TYLKO podczas testowania i jest wyjątkowo hackerski. Ale w przypadkach, gdy potrzebujesz konfiguracji środowiska w testach, właśnie tego potrzebowałem.

Obejmuje to również niektóre moje drobne poprawki, które pozwalają na działanie kodu w obu systemach Windows

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)

jak również uruchomione Centos

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)

Implementacja:

/**
 * 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
źródło
7

Okazuje się, że rozwiązanie @ pushy / @ anonymous / @ Edward Campbell nie działa na Androidzie, ponieważ Android nie jest tak naprawdę Javą. W szczególności Android wcale nie ma java.lang.ProcessEnvironment. Ale w Androidzie okazuje się łatwiejsze, wystarczy wykonać wywołanie JNI do POSIX setenv():

W języku 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;
}

I w Javie:

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
źródło
5

Jak większość ludzi, którzy znaleźli ten wątek, pisałem kilka testów jednostkowych i potrzebowałem zmodyfikować zmienne środowiskowe, aby ustawić odpowiednie warunki do uruchomienia testu. Jednak znalazłem, że najbardziej uprzywilejowane odpowiedzi miały pewne problemy i / lub były bardzo tajemnicze lub zbyt skomplikowane. Mamy nadzieję, że pomoże to innym w szybszym rozwiązaniu problemu.

Po pierwsze, w końcu uznałem rozwiązanie @Hubert Grzeskowiak za najprostsze i zadziałało dla mnie. Chciałbym najpierw do tego dojść. Opiera się na odpowiedzi @Edward Campbell, ale bez komplikacji dla wyszukiwania w pętli.

Zacząłem jednak od rozwiązania @ pushy, które uzyskało najwięcej pozytywnych opinii. Jest to kombinacja @anonymous i @Edward Campbell's. @pushy twierdzi, że oba podejścia są potrzebne, aby objąć zarówno środowisko Linux, jak i Windows. Pracuję pod OS X i stwierdzam, że oba działają (po rozwiązaniu problemu z podejściem @anonymous). Jak zauważyli inni, to rozwiązanie działa przez większość czasu, ale nie wszystkie.

Myślę, że źródłem większości nieporozumień jest rozwiązanie @ anonimowe działające w polu „Środowisko”. Patrząc na definicję struktury ProcessEnvironment , „środowisko” nie jest Mapą <Ciąg, Ciąg>, ale raczej Mapą <Zmienna, Wartość>. Czyszczenie mapy działa poprawnie, ale operacja putAll odbudowuje mapę Map <String, String>, co potencjalnie powoduje problemy, gdy kolejne operacje działają na strukturze danych przy użyciu normalnego interfejsu API, który oczekuje Map <Variable, Value>. Problemem jest także dostęp do / usuwanie poszczególnych elementów. Rozwiązaniem jest dostęp do „Środowiska” pośrednio poprzez „Środowisko modyfikowalne”. Ale ponieważ jest to typ UnmodifiableMapdostępu należy dokonać za pośrednictwem prywatnej zmiennej „m” typu UnmodifiableMap. Zobacz getModifiableEnvironmentMap2 w kodzie poniżej.

W moim przypadku musiałem usunąć niektóre zmienne środowiskowe z mojego testu (inne powinny pozostać niezmienione). Następnie chciałem przywrócić zmienne środowiskowe do ich poprzedniego stanu po teście. Poniższe procedury ułatwiają to. Przetestowałem obie wersje getModifiableEnvironmentMap na OS X i obie działają równorzędnie. Chociaż w oparciu o komentarze w tym wątku, jeden może być lepszym wyborem niż drugi, w zależności od środowiska.

Uwaga: Nie uwzględniłem dostępu do pola „theCaseInsensitiveEnvironmentField”, ponieważ wydaje się, że jest ono specyficzne dla systemu Windows i nie miałem możliwości go przetestować, ale dodanie go powinno być proste.

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
źródło
Dzięki, to był dokładnie mój przypadek użycia i również pod Mac OS X.
Rafael Gonçalves
Tak bardzo mi się podobało, że wybrałem nieco prostszą wersję dla Groovy, patrz poniżej.
Mike Rodent
4

Grzebiąc w Internecie, wydaje się, że można to zrobić za pomocą JNI. Musiałbyś wtedy wywołać funkcję putenv () z C i (prawdopodobnie) musiałbyś to zrobić w sposób, który działał zarówno w systemie Windows, jak i UNIX.

Jeśli to wszystko można zrobić, z pewnością Java nie byłaby w stanie wesprzeć tego, zamiast wkładać mnie w prostą kurtkę.

Znajomy mówiący w Perlu gdzie indziej sugeruje, że dzieje się tak, ponieważ zmienne środowiskowe są przetwarzane globalnie, a Java dąży do dobrej izolacji dla dobrego projektu.

skiphoppy
źródło
Tak, możesz ustawić środowisko procesów z kodu C. Ale nie liczyłbym na to, że będzie działał w Javie. Istnieje duża szansa, że ​​JVM skopiuje środowisko do obiektów Java String podczas uruchamiania, więc zmiany nie zostaną wykorzystane w przyszłych operacjach JVM.
Darron,
Dzięki za ostrzeżenie, Darron. Prawdopodobnie masz dużą szansę, że masz rację.
skiphoppy
2
@Darron wiele powodów, dla których chciałoby się to zrobić, nie ma nic wspólnego z tym, co JVM uważa za środowisko. (Pomyśl o ustawieniu LD_LIBRARY_PATHprzed wywołaniem Runtime.loadLibrary(); dlopen()wywołanie, które wywołuje, patrzy na rzeczywiste środowisko, a nie na sam pomysł Javy).
Charles Duffy
Działa to w przypadku podprocesów uruchamianych przez rodzimą bibliotekę (która w moim przypadku jest ich większość), ale niestety nie działa w przypadku podprocesów uruchamianych przez klasy Process lub ProcessBuilder Java.
Dan
4

Próbowałem odpowiedzi pushy powyżej i zadziałało w większości. Jednak w niektórych okolicznościach widziałbym ten wyjątek:

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

Okazuje się to, gdy metoda została wywołana więcej niż raz, ze względu na implementację niektórych wewnętrznych klas. ProcessEnvironment.Jeśli setEnv(..)metoda jest wywoływana więcej niż raz, gdy klucze są pobierane z theEnvironmentmapy, są one teraz ciągami znaków (zostały wstawione jako ciągi przy pierwszym wywołaniu setEnv(...)) i nie mogą być rzutowane na ogólny typ mapy, Variable,który jest prywatną klasą wewnętrznąProcessEnvironment.

Naprawiona wersja (w Scali) znajduje się poniżej. Mam nadzieję, że przeniesienie do Javy nie jest zbyt trudne.

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
źródło
Gdzie jest zdefiniowany JavaClass?
Mike Slinn,
1
Przypuszczalnie import java.lang.{Class => JavaClass}.
Randall Whitman
1
Implementacja java.lang.ProcessEnvironment jest różna na różnych platformach, nawet dla tej samej kompilacji. Na przykład w implementacji systemu Windows nie ma klasy java.lang.ProcessEnvironment $ Zmienna, ale ta klasa istnieje w jednej dla systemu Linux. Możesz to łatwo sprawdzić. Wystarczy pobrać dystrybucję tar.gz JDK dla systemu Linux i wyodrębnić źródło z pliku src.zip, a następnie porównać z tym samym plikiem z dystrybucji dla systemu Windows. Są zupełnie inne w JDK 1.8.0_181. Nie sprawdziłem ich w Javie 10, ale nie zdziwię się, jeśli będzie taki sam obraz.
Alex Konshin
1

Jest to zła wersja Kotlin od znaku @ nachalny za złą odpowiedź =)

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

Działa przynajmniej w systemie macOS Mojave.

GarouDan
źródło
0

Jeśli pracujesz z SpringBoot, możesz dodać określenie zmiennej środowiskowej w następującej właściwości:

was.app.config.properties.toSystemProperties
Alex
źródło
1
Czy możesz wyjaśnić trochę?
Faraz
0

wariant oparty na odpowiedzi @ pushy działa w systemie 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)

Stosowanie:

old_environ = dict(os.environ)
old_environ['EPM_ORACLE_HOME'] = r"E:\Oracle\Middleware\EPMSystem11R1"
set_env(old_environ)
Keith K.
źródło
0

Odpowiedź Tima Ryana działała dla mnie ... ale chciałem ją dla Groovy (na przykład kontekst Spocka) i 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|"
gryzoń mike
źródło
0

Wersja w Kotlinie, w tym algorytmie stworzyłem dekorator, który pozwala ustawiać i pobierać zmienne ze środowiska.

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
źródło
-1

Implementacja Kotlina, którą niedawno wykonałem na podstawie odpowiedzi Edwarda:

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
źródło
-12

Możesz przekazać parametry do początkowego procesu Java za pomocą -D:

java -cp <classpath> -Dkey1=value -Dkey2=value ...
matowy b
źródło
Wartości nie są znane w czasie wykonywania; stają się znane podczas wykonywania programu, gdy użytkownik je udostępnia / wybiera. I to ustawia tylko właściwości systemowe, a nie zmienne środowiskowe.
skiphoppy
W takim przypadku prawdopodobnie chcesz znaleźć regularny sposób (poprzez parametr args [] do metody głównej), aby wywołać swoje podprocesy.
matt b
matt b, zwykły sposób odbywa się za pośrednictwem ProcessBuilder, jak wspomniano w moim pierwotnym pytaniu. :)
skiphoppy
7
-D parametry są dostępne przez System.getPropertyi nie są takie same jak System.getenv. Poza tym Systemklasa pozwala również ustawić te właściwości statycznie za pomocąsetProperty
anirvan