Як встановити змінні середовища з Java?


289

Як встановити змінні середовища з Java? Я бачу, що я можу це зробити для використання підпроцесів ProcessBuilder. Однак у мене є кілька підпроцесів, тому я б краще змінив середовище поточного процесу і дозволив підпроцесам наслідувати його.

Існує System.getenv(String)для отримання єдиної змінної середовища. Я також можу отримати Mapповний набір змінних середовища System.getenv(). Але, якщо закликати put()до цього, Mapце UnsupportedOperationExceptionозначає - вони, мабуть, означають, що навколишнє середовище має бути лише для читання. І, немає System.setenv().

Отже, чи є спосіб встановити змінні середовища в поточному запущеному процесі? Якщо так, то як? Якщо ні, то яке обґрунтування? (Це тому, що це Java, і тому я не повинен робити злісних нерепортажних застарілих речей, таких як торкання свого оточення?) підпроцеси?


System.getEnv () призначений для універсального пошуку, деякі середовища навіть не мають змінних середовищ.
b1nary.atr0phy

7
Для тих , хто потребував в цьому для одиничного використання тестування випадку: stackoverflow.com/questions/8168884 / ...
Atifm

Відповіді:


88

(Це тому, що це Java, і тому я не повинен робити злісних нерепортажних застарілих речей, таких як торкання свого оточення?)

Я думаю, ти вдарив цвях по голові.

Можливим способом полегшити тягар було б визначити метод

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

і пропустити будь-який ProcessBuilders через нього перед їх запуском.

Також ви, напевно, це вже знаєте, але ви можете почати більше одного процесу з тим самим ProcessBuilder. Отже, якщо ваші підпроцеси однакові, вам не потрібно робити це налаштування знову і знову.


1
Прикро управління не дозволить мені використовувати іншу портативну мову для запуску цього набору злих, застарілих підпроцесів. :)
skiphoppy

18
С.Лотт, я не хочу налаштувати батьківське оточення. Я хочу встановити своє власне оточення.
skiphoppy

3
Це чудово працює, якщо тільки не чиясь бібліотека (наприклад, Sun) запускає процес.
sullivan-

24
@ b1naryatr0phy Ви пропустили бал. Ніхто не може грати зі своїми змінними середовища, оскільки ці змінні є локальними для процесу (те, що ви встановили в Windows, є значеннями за замовчуванням). Кожен процес може вільно змінювати власні змінні ... якщо тільки його Java.
maaartinus

9
Це обмеження java трохи витягує копа. Немає жодних причин, щоб java не дозволяла вам встановлювати env vars, окрім "тому що ми не хочемо, щоб java цього робила".
IanNorton

232

Для використання в сценаріях, коли вам потрібно встановити конкретні значення середовища для одиничних тестів, вам може бути корисний наступний хак. Це змінить змінні середовища в усьому JVM (тому переконайтеся, що ви скинули будь-які зміни після тесту), але не змінить системне середовище.

Я виявив, що комбінація двох брудних хак від Едварда Кемпбела та анонімних працює найкраще, оскільки одна з них не працює під Linux, а одна не працює під Windows 7. Отже, щоб отримати зловмисний мультиплатформенний злом, я поєднав їх:

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

Це працює як шарм. Повні кредити двох авторів цих хак.


1
Чи це зміниться лише в пам'яті, чи фактично змінить всю змінну середовища в системі?
Шервін Асгарі

36
Це змінить лише змінну середовища в пам'яті. Це добре для тестування, оскільки ви можете встановити змінну оточення як необхідну для свого тесту, але залишити занурення в системі такими, якими вони є. Насправді я б сильно заважав комусь використовувати цей код для будь-яких інших цілей, ніж тестування. Цей код злий ;-)
напористий

9
Як FYI, JVM створює копію змінних середовища, коли він запускається. Це буде редагувати цю копію, а не змінні середовища для батьківського процесу, який запустив JVM.
bmeding

Я спробував це на Android, і, схоже, це не взяло. Хтось ще має удачу в Android?
Ганс-Крістоф Штайнер

5
Звичайно,import java.lang.reflect.Field;
напористий

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

Або додати / оновити єдиний вар і видалити цикл відповідно до пропозиції 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
Здається, це змінило б карту в пам'яті, але чи збереже це значення для системи?
Джон Онстотт

1
добре, це все-таки змінює карту пам'яті змінних середовища. Я здогадуюсь, цього достатньо для багатьох випадків використання. @Edward - чорт, важко уявити, як це рішення було розібране в першу чергу!
анірван

13
Це не змінить змінних середовища в системі, але змінить їх у поточному виклику Java. Це дуже корисно для тестування одиниць.
Стюарт К

10
чому б не використовувати Class<?> cl = env.getClass();замість цього для циклу?
thejoshwolfe

1
Це саме те, що я шукав! Я писав тести інтеграції для деякого коду, який використовує інструмент сторонніх розробників, який чомусь лише дозволяє змінювати його абсурдно коротку довжину часу за замовчуванням за допомогою змінної середовища.
Девід Демар

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

на Android інтерфейс відкривається через Libcore.os як своєрідний прихований API.

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

Клас Libcore, а також ОС інтерфейсу є загальнодоступними. Просто декларація класу відсутня, і її потрібно показати посилання. Не потрібно додавати класи до програми, але це також не зашкодить, якщо воно включене.

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
Тестовано та працює на Android 4.4.4 (CM11). PS Єдине , регулювання я зробив заміняв throws ErrnoExceptionз throws Exception.
DavisNT

7
API 21, має Os.setEnv. developer.android.com/reference/android/system/… , java.lang.String, boolean)
Джаред Берроуз

1
Потенційно
відключіться

13

Лише Linux

Встановлення єдиних змінних середовища (на основі відповіді Едварда Кемпбела):

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

Використання:

Спочатку покладіть метод у будь-який клас, який ви хочете, наприклад, SystemUtil. Тоді називайте це статично:

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

Якщо ви подзвоните System.getenv("SHELL")після цього, ви "/bin/bash"повернетесь.


Вище не працює в Windows 10, але буде працювати в Linux.
mengchengfeng

Цікаво. Я сам не пробував цього в Windows. Ви отримуєте помилку, @mengchengfeng?
Hubert Grzeskowiak

@HubertGrzeskowiak Ми не бачили жодних повідомлень про помилки, просто не працювали ...
mengchengfeng

9

Це комбінація відповіді @ paul-blair, перетвореної на Java, яка включає деякі очищення, на які вказує paul blair, та деякі помилки, які, здається, були всередині коду @pushy, який складається з @Edward Campbell та анонімного.

Я не можу підкреслити, наскільки цей код повинен використовуватися ТІЛЬКИ в тестуванні і є надзвичайно хитким. Але для випадків, коли вам потрібна настройка середовища в тестах, це саме те, що мені потрібно.

Сюди також входять деякі мої дотики, які дозволяють коду працювати в обох 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)

а також 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)

Впровадження:

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

Виявляється, рішення від @ pushy / @ anonymous / @ Edward Campbell не працює на Android, оскільки Android насправді не є Java. Зокрема, Android взагалі немає java.lang.ProcessEnvironment. Але в Android виходить простіше, вам просто потрібно здійснити дзвінок JNI на POSIX setenv():

В С / 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;
}

І на 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

Як і більшість людей, які знайшли цю тему, я писав кілька тестових одиниць і мені потрібно було змінити змінні середовища, щоб встановити правильні умови для запуску тесту. Однак я виявив, що найбільш відповідні відповіді мали деякі питання та / або були дуже виразними або надто складними. Сподіваємось, це допоможе іншим швидше розібратися з рішенням.

По-перше, я нарешті знайшов рішення @Hubert Grzeskowiak найпростішим, і воно працювало для мене. Я б хотів, щоб я прийшов до цього першим. Він заснований на відповіді @Edward Кемпбелла, але без ускладнень для пошуку циклу.

Однак я почав з рішення @ pushy, яке отримало найбільше відгуків. Це комбо @anonymous та @Edward Campbell's. @pushy стверджує, що обидва підходи потрібні для покриття обох середовищ Linux та Windows. Я працюю в OS X і виявляю, що обидва працюють (раз виправлена ​​проблема з @anonymous підходом). Як зазначали інші, це рішення працює більшу частину часу, але не всі.

Я думаю, що джерело більшої частини плутанини походить від рішення @ anonymous, яке працює в полі "TheEl Environment". Дивлячись на визначення структури ProcessEnvironment , "довкілля" - це не карта <String, String>, а скоріше це Map <Змінна, Значення>. Очищення карти працює нормально, але операція putAll відновлює карту Map <String, String>, яка потенційно спричиняє проблеми при наступних операціях над структурою даних за допомогою звичайного API, який очікує на карту <Змінна, Значення>. Також проблема / доступ до окремих елементів є проблемою. Рішення полягає в тому, щоб отримати доступ до "довкілля" опосередковано через "незмінне середовище". Але оскільки це тип UnmodifiableMapдоступ повинен здійснюватися через приватну змінну 'm' типу UnmodifiableMap. Дивіться getModifiableEnvironmentMap2 у коді нижче.

У моєму випадку мені потрібно було видалити деякі змінні середовища для мого тесту (інші повинні бути незмінними). Тоді я хотів відновити змінні середовища до їх попереднього стану після тесту. Підпрограми внизу роблять це прямо вперед. Я протестував обидві версії getModifiableEnvironmentMap на OS X, і обидві працюють рівнозначно. Хоча на основі коментарів у цій темі, один може бути кращим вибором, ніж інший, залежно від середовища.

Примітка. Я не включав доступ до "theCaseInsensitiveEnvironmentField", оскільки це, здається, є специфічним для Windows, і я не мав можливості його перевірити, але додавати його слід прямо.

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

Дякую, це був саме мій варіант використання і під Mac OS x теж.
Рафаель Гонсальвес

Мені так сподобалось, що я зашумав трохи простішу версію для Groovy, дивіться нижче.
гризун

4

Покіршившись в Інтернеті, схоже, що це можливо зробити за допомогою JNI. Тоді вам доведеться зателефонувати на putenv () з C, і вам (мабуть) доведеться це зробити так, як це працювало і в Windows, і в UNIX.

Якщо все, що можна зробити, для Java, напевно, не буде занадто важко підтримати це, а не надіти мене в пряму куртку.

Друг, який розмовляє Perl в іншому місці, припускає, що це тому, що змінні середовища є глобальними процесами, і Java прагне до хорошої ізоляції для гарного дизайну.


Так, ви можете встановити середовище процесів із коду С. Але я б не розраховував на те, що працює на Java. Є хороший шанс, що JVM копіює середовище в об'єкти Java String під час запуску, тому ваші зміни не використовуються для майбутніх операцій JVM.
Даррон

Дякую за попередження, Даррон. Напевно, є хороший шанс, що ти маєш рацію.
skiphoppy

2
@Darron Багато причин, з яких хотілося б це зробити, взагалі не мають нічого спільного з тим, що JVM вважає, що навколишнє середовище. (Подумайте про налаштування LD_LIBRARY_PATHперед викликом Runtime.loadLibrary(); dlopen()дзвінок, який він викликає, дивиться на реальне середовище, а не на ідею Java того самого).
Чарльз Даффі

Це працює для підпроцесів, розпочатих у власній бібліотеці (яка в моєму випадку є більшістю), але, на жаль, не працює для підпроцесів, розпочатих класами Process або ProcessBuilder Java.
Дан

4

Спробував відповідь напоказ вище, і це спрацювало здебільшого. Однак за певних обставин я бачив би це виняток:

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

Це виявляється, коли метод викликався не один раз, завдяки реалізації певних внутрішніх класів. ProcessEnvironment.Якщо setEnv(..)метод викликається більше одного разу, коли ключі витягуються з theEnvironmentкарти, вони тепер є рядками (введені в як рядки при першому виклику setEnv(...)) і не можуть бути передані загальному типу карти, Variable,який є приватним внутрішнім класомProcessEnvironment.

Фіксована версія (у Scala), наведена нижче. Сподіваємось, не дуже складно перенестись на 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()
  }
}

Де визначено JavaClass?
Майк Слінн

1
Імовірно import java.lang.{Class => JavaClass}.
Рандалл Вітмен

1
Впровадження java.lang.ProcessEnvironment відрізняється на різних платформах навіть для однієї збірки. Наприклад, немає класу java.lang.ProcessEnvironment $ Variable у впровадженні Windows, але цей клас існує в одному для Linux. Ви можете легко це перевірити. Просто завантажте дистрибутив JDK tar.gz для Linux і витягніть джерело з src.zip, а потім порівняйте його з тим самим файлом з дистрибутива для Windows. Вони абсолютно різні в JDK 1.8.0_181. Я не перевіряв їх на Java 10, але не здивуюсь, якщо є однакова картинка.
Алекс Коншин

1

Це злий варіант Котлінської злої відповіді @ 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)
            }
        }
    }

Він працює принаймні в macOS Mojave.


0

Якщо ви працюєте з SpringBoot, ви можете додати вказану змінну навколишнього середовища в наступному властивості:

was.app.config.properties.toSystemProperties

1
Чи можете ви поясніть трохи?
Фараз

0

варіант, заснований на відповіді @ pushy , працює на 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)

Використання:

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

0

Відповідь Тіма Райана працювала на мене ... але я хотів це для Groovy (наприклад, Спокського контексту) та спрощеного:

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

Версія в Котліні, в цьому алгоритмі я створив декоратор, який дозволяє встановлювати та отримувати змінні з оточення.

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

Реалізацію Котліна я нещодавно зробив на основі відповіді Едварда:

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

Ви можете передавати параметри у свій початковий процес Java за допомогою -D:

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

Значення невідомі під час виконання; вони стають відомими під час виконання програми, коли користувач надає / вибирає їх. І це встановлює лише системні властивості, а не змінні середовища.
skiphoppy

Тоді в цьому випадку ви, ймовірно, хочете знайти звичайний спосіб (через параметр args [] до основного методу) викликати свої підпроцеси.
matt b

matt b, звичайний спосіб - це через ProcessBuilder, про що говорилося в моєму первісному запитанні. :)
skiphoppy

7
-D параметри доступні через System.getPropertyта не є такими, як System.getenv. Крім того, Systemклас також дозволяє встановити ці властивості статично, використовуючиsetProperty
anirvan
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.