Как установить переменные окружения из Java?

290

Как установить переменные окружения из Java? Я вижу, что я могу сделать это для использования подпроцессов ProcessBuilder. У меня есть несколько подпроцессов для запуска, поэтому я бы предпочел изменить среду текущего процесса и позволить подпроцессам наследовать ее.

Есть System.getenv(String)для получения одной переменной среды. Я также могу получить Mapполный набор переменных окружения с System.getenv(). Но, призывая put()к этому Mapбросает UnsupportedOperationException- очевидно, они означают, что среда должна быть только для чтения. И нет System.setenv().

Итак, есть ли способ установить переменные среды в текущем процессе? Если да, то как? Если нет, в чем смысл? (Это потому, что это Java, и поэтому я не должен делать злых непереносимых устаревших вещей, таких как прикосновение к моей среде?) И если нет, то любые полезные предложения по управлению изменениями переменных среды, которые я собираюсь передать нескольким подпроцессы?

skiphoppy
источник
System.getEnv () должен быть универсальным, в некоторых средах даже нет переменных окружения.
b1nary.atr0phy
7
Для тех, кто нуждался в этом для случая использования модульного тестирования: stackoverflow.com/questions/8168884/…
Atifm
Для Scala, используйте это: gist.github.com/vpatryshev/b1bbd15e2b759c157b58b68c58891ff4
Влад

Ответы:

88

(Это потому, что это Java, и поэтому я не должен совершать злые непереносимые устаревшие вещи, такие как прикосновение к моей среде?)

Я думаю, что ты ударил гвоздь по голове.

Возможный способ облегчить бремя - это выделить метод

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

и передайте любые ProcessBuilders через это прежде, чем начать их.

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

Майкл Майерс
источник
1
Это позор, управление не позволит мне использовать другой переносимый язык для запуска этого набора злых, устаревших подпроцессов. :)
skiphoppy
18
С.Лотт, я не хочу устанавливать среду родителей. Я хочу установить свою собственную среду.
skiphoppy
3
Это прекрасно работает, если только не чья-либо библиотека (например, Sun) запускает процесс.
Салливан
24
@ b1naryatr0phy Вы упустили момент. Никто не может играть с вашими переменными среды, поскольку эти переменные являются локальными для процесса (то, что вы устанавливаете в Windows, является значениями по умолчанию). Каждый процесс может изменять свои собственные переменные ... если только не Java.
Maaartinus
9
Это ограничение Ява немного отыгрывается. Нет никаких оснований для того, чтобы java не разрешал вам устанавливать переменные env, кроме «потому что мы не хотим, чтобы 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
Это только изменит переменную среды в памяти. Это хорошо для тестирования, потому что вы можете установить переменную окружения, необходимую для вашего теста, но оставить envs в системе как есть. На самом деле, я настоятельно рекомендую любому использовать этот код для любых других целей, кроме тестирования. Этот код является злом ;-)
настойчивый
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);
  }
Kashyap
источник
3
Похоже, это изменит карту в памяти, но сохранит ли это значение в системе?
Джон Онстотт
1
хорошо это меняет карту памяти переменных среды. Я думаю, что этого достаточно во многих случаях использования. @ Эдвард - черт возьми, трудно представить, как это решение было разработано с самого начала!
Anirvan
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);
}
анонимный
источник
18

в 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;
}
user3404318
источник
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
Потенциально не функционировать теперь с новыми ограничениями Пирога: developer.android.com/about/versions/pie/…
TWiStErRob
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?
Юбер Гжесковяк,
@HubertGrzeskowiak Мы не увидели никаких сообщений об ошибках, просто они не работали ...
mengchengfeng
9

Это комбинация ответа @ paul-blair, преобразованного в Java, который включает в себя некоторые исправления, указанные Полом Блэром, и некоторые ошибки, которые, похоже, были внутри кода @pushy, который состоит из @Edward Campbell и anonymous.

Я не могу подчеркнуть, насколько этот код должен использоваться ТОЛЬКО в тестировании, и он очень хакерский. Но для случаев, когда вам нужна настройка среды в тестах, это именно то, что мне нужно.

Это также включает в себя некоторые мелкие штрихи, которые позволяют коду работать на обеих 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));
}
mangusbrother
источник
7

Оказывается, решение от @ pushy / @ anonymous / @ Edward Campbell не работает на Android, потому что Android на самом деле не Java. Конкретно у Android нет java.lang.ProcessEnvironmentвообще. Но в Android это оказывается проще, вам просто нужно сделать JNI-вызов в POSIX setenv():

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

И на Яве:

public class Posix {

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

    private void runTest() {
        Posix.setenv("LD_LIBRARY_PATH", "foo", true);
    }
}
Ханс-Кристоф Штайнер
источник
6

Как и большинство людей, которые нашли этот поток, я писал некоторые модульные тесты, и мне нужно было изменить переменные среды, чтобы установить правильные условия для выполнения теста. Тем не менее, я обнаружил, что ответы с наибольшим количеством голосов имели некоторые проблемы и / или были очень загадочными или чрезмерно сложными. Надеюсь, это поможет другим быстрее разобраться в решении.

Во-первых, я наконец нашел решение @Hubert Grzeskowiak самым простым, и оно сработало для меня. Я хотел бы прийти к этому первым. Это основано на ответе Эдварда Кэмпбелла, но без усложнения циклического поиска.

Однако я начал с решения @ pushy, которое получило наибольшее количество голосов. Это комбинация @anonymous и @Edward Campbell's. @pushy утверждает, что оба подхода необходимы для охвата как среды Linux, так и Windows. Я работаю под OS X и обнаружил, что оба работают (как только проблема с подходом @anonymous решена). Как уже отмечали другие, это решение работает большую часть времени, но не все.

Я думаю, что источником большей части путаницы является решение @ anonymous, работающее в области «TheEnvironment». Глядя на определение структуры ProcessEnvironment , «theEnvironment» - это не карта <String, String>, а карта <переменная, значение>. Очистка карты работает нормально, но операция putAll перестраивает карту Map <String, String>, что потенциально вызывает проблемы, когда последующие операции работают с структурой данных с использованием обычного API, который ожидает Map <Variable, Value>. Кроме того, доступ / удаление отдельных элементов является проблемой. Решение состоит в том, чтобы получить доступ к «Среде» косвенно через «Немодифицируемую Среду». Но так как это тип 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 стремится к хорошей изоляции для хорошего дизайна.

skiphoppy
источник
Да, вы можете установить среду процессов из C-кода. Но я бы не рассчитывал, что это будет работать на Java. Существует большая вероятность, что JVM копирует среду в объекты Java String во время запуска, поэтому ваши изменения не будут использоваться для будущих операций JVM.
Даррон
Спасибо за предупреждение, Даррон. Вероятно, есть хороший шанс, что ты прав.
Скифоппи
2
@ Darron многие из причин, по которым можно это сделать, не имеют ничего общего с тем, что JVM считает окружающей средой. (Думайте об установке LD_LIBRARY_PATHперед вызовом Runtime.loadLibrary(); dlopen()вызов, который он вызывает, смотрит на реальную среду, а не на идею Java того же самого).
Чарльз Даффи
Это работает для подпроцессов, запускаемых собственной библиотекой (которая в моем случае является большинством из них), но, к сожалению, не работает для подпроцессов, запускаемых Java-классами Process или ProcessBuilder.
Дан
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 различна на разных платформах даже для одной и той же сборки. Например, в реализации Windows нет класса java.lang.ProcessEnvironment $ Variable, но этот класс существует в одном для Linux. Вы можете легко проверить это. Просто скачайте tar.gz JDK для Linux и извлеките исходный код из src.zip, затем сравните его с тем же файлом из дистрибутива для Windows. Они абсолютно разные в JDK 1.8.0_181. Я не проверял их в Java 10, но я не удивлюсь, если будет такая же картина.
Алексей Коншин
1

Это версия Kotlin злой ответ @ Pushy's злой =)

@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 по крайней мере.

GarouDan
источник
0

Если вы работаете с SpringBoot, вы можете добавить указание переменной среды в следующем свойстве:

was.app.config.properties.toSystemProperties
Alex
источник
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

Версия в Kotlin, в этом алгоритме я создал декоратор, который позволяет устанавливать и получать переменные из среды.

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

Реализация Kotlin, которую я недавно сделал, основываясь на ответе Эдварда:

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
источник
-12

Вы можете передать параметры в исходный процесс Java с помощью -D:

java -cp <classpath> -Dkey1=value -Dkey2=value ...
Мэтт Б
источник
Значения не известны во время выполнения; они становятся известны во время выполнения программы, когда пользователь предоставляет / выбирает их. И это устанавливает только системные свойства, а не переменные среды.
Скифоппи
Тогда в этом случае вы, вероятно, захотите найти обычный способ (через параметр args [] метода main) вызвать ваши подпроцессы.
Matt B
Matt B, обычный путь через ProcessBuilder, как упоминалось в моем первоначальном вопросе. :)
skiphoppy
7
Параметры -D доступны через System.getPropertyи не совпадают с System.getenv. Кроме того, Systemкласс также позволяет устанавливать эти свойства статически, используяsetProperty
anirvan