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

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

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

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

System.getEnv () призван быть универсальным, в некоторых средах даже нет переменных среды.

b1nary.atr0phy 12.05.2012 04:44

Для тех, кому это нужно для использования в модульном тестировании: stackoverflow.com/questions/8168884/…

Atifm 12.06.2017 18:21

Для Scala используйте это: gist.github.com/vpatryshev/b1bbd15e2b759c157b58b68c58891ff4

Vlad Patryshev 15.01.2020 00:20
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
321
3
444 708
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

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

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

Значения неизвестны во время выполнения; они становятся известны во время выполнения программы, когда пользователь предоставляет / выбирает их. И это устанавливает только системные свойства, а не переменные среды.

skiphoppy 25.11.2008 20:45

Тогда в этом случае вы, вероятно, захотите найти обычный способ (через параметр args [] основного метода) для вызова ваших подпроцессов.

matt b 25.11.2008 20:53

matt b, обычный способ - через ProcessBuilder, как упоминалось в моем исходном вопросе. :)

skiphoppy 25.11.2008 21:08

Параметры -D доступны через System.getProperty и не совпадают с System.getenv. Кроме того, класс System также позволяет устанавливать эти свойства статически с помощью setProperty.

anirvan 28.07.2010 11:14
Ответ принят как подходящий

(Is it because this is Java and therefore I shouldn't be doing evil nonportable obsolete things like touching my environment?)

Я думаю, вы попали в самую точку.

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

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

и пропустите через него любые ProcessBuilder перед их запуском.

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

Жаль, что руководство не позволяет мне использовать другой переносимый язык для запуска этого набора злых, устаревших подпроцессов. :)

skiphoppy 25.11.2008 20:47

@skiphoppy: ни один инструмент не может установить родительскую среду - это не проблема инструмента, это проблема ОС.

S.Lott 25.11.2008 21:04

С.Лотт, я не собираюсь настраивать родительскую среду. Я хочу создать свою собственную среду.

skiphoppy 25.11.2008 21:06

Спасибо, mmyers. Я думаю, мне определенно придется исключить такой метод. У меня уже есть класс ProcessUtils, поэтому он должен расти, чтобы управлять изменениями в среде.

skiphoppy 25.11.2008 21:07

Это отлично работает, если только это не чья-то библиотека (например, Sun), которая запускает процесс.

sullivan- 26.01.2012 07:16

«Я думаю, вы попали в самую точку». Не могу согласиться, последнее, чего я (как пользователь) хочу, - это ваша или какая-либо другая программа, работающая с моими переменными среды.

b1nary.atr0phy 12.05.2012 04:40

@ b1naryatr0phy Вы упустили суть. Никто не может играть с переменные вашей среды, поскольку эти переменные являются локальными для процесса (то, что вы устанавливаете в Windows, является значениями по умолчанию). Каждый процесс может изменять свои собственные переменные ... кроме Java.

maaartinus 29.06.2012 04:36

В Microsoft .Net есть метод, позволяющий изменять переменные среды текущего процесса: msdn.microsoft.com/en-us/library/…. В моем последнем проекте он использовался для автоматической настройки путей к проприетарным библиотекам.

kevinarpe 21.05.2013 17:41

Это ограничение Java немного помогает. Нет никаких причин для того, чтобы java не позволял вам устанавливать env vars, кроме «потому что мы не хотим, чтобы java это делала».

IanNorton 27.02.2017 12:01

Покопавшись в сети, похоже, что это можно сделать с помощью JNI. Затем вам нужно будет вызвать putenv () из C, и вы (предположительно) должны будете сделать это так, чтобы это работало как в Windows, так и в UNIX.

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

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

Да, вы можете установить среду процессов из кода C. Но я бы не стал рассчитывать, что это будет работать на Java. Существует большая вероятность того, что JVM копирует среду в объекты Java String во время запуска, поэтому ваши изменения не будут использоваться для будущих операций JVM.

Darron 26.11.2008 00:09

Спасибо за предупреждение, Даррон. Вероятно, вы правы.

skiphoppy 26.11.2008 00:10

@Darron: многие причины, по которым можно было бы это сделать, вообще не имеют ничего общего с тем, что JVM думает о среде. (Подумайте о настройке LD_LIBRARY_PATH перед вызовом Runtime.loadLibrary(); вызов dlopen(), который он вызывает, смотрит на среду настоящий, а не на идею Java).

Charles Duffy 29.10.2013 18:55

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

Dan 09.08.2014 00:33
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);
        }
    }
}

Или добавить / обновить одну переменную и удалить цикл в соответствии с предложением 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);
  }

Похоже, это изменит карту в памяти, но сохранит ли это значение в системе?

Jon Onstott 05.12.2009 20:53

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

anirvan 27.07.2010 16:53

Это не изменит переменные среды в системе, но изменит их при текущем вызове Java. Это очень полезно для модульного тестирования.

Stuart K 14.02.2011 17:07

Этот не работает в Windows 7 x64; один с theCaseInsensitiveEnvironment делает.

Stevo Slavić 19.05.2011 21:35

почему бы не использовать Class<?> cl = env.getClass(); вместо цикла for?

thejoshwolfe 09.05.2012 07:00

Это именно то, что я искал! Я писал интеграционные тесты для некоторого кода, который использует сторонний инструмент, который по какой-то причине позволяет изменять только абсурдно короткий тайм-аут по умолчанию с помощью переменной окружения.

David DeMar 15.07.2015 05:05

У меня не работает в моем интеграционном тесте, но анонимный ответ работает.

Bat0u89 13.02.2020 19:18
// 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);
}

Для использования в сценариях, где вам нужно установить определенные значения среды для модульных тестов, вам может пригодиться следующий прием. Он изменит переменные среды во всей 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);
      }
    }
  }
}

Это работает как шарм. Полное спасибо двум авторам этих хаков.

Изменится ли это только в памяти или фактически изменится вся переменная среды в системе?

Shervin Asgari 25.06.2012 11:12

Это изменит только переменную окружения в памяти. Это хорошо для тестирования, потому что вы можете установить переменную среды по мере необходимости для вашего теста, но оставить env в системе такими, какие они есть. Фактически, я настоятельно рекомендую никому не использовать этот код для каких-либо других целей, кроме тестирования. Этот код злой ;-)

pushy 25.06.2012 12:09

К вашему сведению, JVM создает копию переменных среды при запуске. Это отредактирует эту копию, а не переменные среды для родительского процесса, запустившего JVM.

bmeding 15.08.2012 18:12

Я пробовал это на Android, и, похоже, это не помогло. Кому-нибудь еще повезло с Android?

Hans-Christoph Steiner 04.09.2012 22:23

Конечно, import java.lang.reflect.Field;

pushy 21.01.2013 13:44

@pushy Process Explorer не показывает вновь установленную переменную. Я использую Java 1.7.0_45 и первым делом вызываю setEnv () в main ().

Aleksandr Dubinsky 27.10.2013 19:11

Спасибо за отзыв, какую ОС вы используете? В ближайшие дни я постараюсь протестировать код с последней версией java.

pushy 28.10.2013 11:49

Не вызывает setenv(), поэтому, хотя Посмотрите может выглядеть так, как будто он работает, на самом деле он вообще не меняет переменные среды, а просто меняет карту Java, которая их отображает. Если вы мне не верите, посмотрите /proc/PID/environ (в Linux).

Charles Duffy 29.10.2013 18:53

Я считаю, что ты в порядке. Кроме того, это именно то, что нужно большинству людей. Это очень грязный прием для изменения переменных среды во время модульного тестирования. Изменения не будут (и не должны быть видны) за пределами JVM. Также этот вопрос уже обсуждался в комментариях №3 и №4. Возможно, стоит указать этот факт в тексте ответа.

pushy 29.10.2013 19:19

Код в блоке catch отлично работает с Android.

Jared Burrows 21.06.2015 07:34

@pushy сегмент кода cl.getDeclaredField("m"); также выдает NoSuchFieldException, поэтому нам может потребоваться добавить его в объявление метода, иначе нам понадобится еще один try catch в исходном catch (NoSuchFieldException e)

Kasun Siyambalapitiya 26.09.2017 14:05

Если вы хотите избежать жесткого кодирования строки класса и выполнения этого цикла для поиска правильного класса, я обнаружил, что этот подход сработал: var unmodifiablemapClass = Collections.unmodifiableMap(new HashMap<>()).getClass() (код не протестирован, поскольку я на самом деле написал его на Kotlin, но вы поняли идею)

Rik 04.07.2018 10:17

Мне также нужно было добавить cienv.clear (); и env.clear (); поскольку мой модульный тест требовал проверки, что, если env не был установлен, будет показана правильная ошибка. Однако это отличный набор кода для решения проблемы!

Karl Henselin 24.10.2018 20:41

добавьте newenv.values().removeIf(Objects::isNull), чтобы избежать потенциальных ошибок

Kevin 26.04.2019 06:01

Я пытался использовать это в моем проекте gradle, так как я стремился автоматически найти и установить переменную среды ANDROID_HOME, если она еще не была определена. Но похоже, что этот код может очищать другие переменные среды (в уловке у нас есть это), еще одна вещь заключается в том, что мы позволяем нашим полям быть доступными, что не может быть хорошо, и я не знаю почему, но это тоже вызывало другую проблему. Итак, пока я не использую его, но было бы неплохо найти для него улучшения.

GarouDan 28.04.2019 19:48

Каким же тогда было бы решение для производства?

Arttu 22.11.2019 18:10

Отлично работает @pushy! @Arttu наилучшим значительным решением для производства было бы определение этих переменных среды на сервере.

jacobcs 29.04.2020 04:45

Пробовал настойчивый ответ выше, и он по большей части работал. Однако при определенных обстоятельствах я бы увидел это исключение:

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?

Mike Slinn 20.12.2014 03:00

Предположительно import java.lang.{Class => JavaClass}.

Randall Whitman 29.09.2017 23:12

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

Alex Konshin 25.07.2018 04:05

Оказывается, решение от @ 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;
}

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

на 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;
}

Протестировано и работает на Android 4.4.4 (CM11). P.S. Единственная корректировка, которую я сделал, - это замена throws ErrnoException на throws Exception.

DavisNT 23.08.2014 15:50

API 21 теперь имеет Os.setEnv. developer.android.com/reference/android/system/…, java.lang.String, логическое)

Jared Burrows 21.06.2015 07:24

Потенциально больше не существует с новыми ограничениями Pie: developer.android.com/about/versions/pie/…

TWiStErRob 24.09.2018 11:34

Это комбинация ответа @ paul-blair, преобразованного в Java, который включает некоторые очистки, указанные Полом Блэром, и некоторые ошибки, которые, похоже, были внутри кода @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));
}

это работает, используйте так: setenv("coba","coba value");System.out.println(System.getenv("coba")); результат: Set environment variable <coba> to <coba value>. Sanity Check: cobavalue coba value

Mang Jojot 06.08.2020 08:02

Только Linux / MacOS

Установка одиночных переменных среды (на основе ответа Эдварда Кэмпбелла):

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 25.09.2017 23:36

Интересно. Сам на винде не пробовал. Вы получаете сообщение об ошибке, @mengchengfeng?

Hubert Grzeskowiak 26.09.2017 06:41

@HubertGrzeskowiak Мы не видели сообщений об ошибках, просто не работало ...

mengchengfeng 27.09.2017 03:14

у меня он работает на windows 10 и linux. Поздравляю!

Bastien Gallienne 27.01.2021 14:10

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

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

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

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

Rafael Gonçalves 01.12.2018 18:20

Это так понравилось, что я сделал немного более простую версию для Groovy, см. Ниже.

mike rodent 29.12.2019 22:48

Реализация 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)
    }
}

Это котлинская злая версия зла @ 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.

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

was.app.config.properties.toSystemProperties

Не могли бы вы немного объяснить?

Faraz 17.03.2020 19:02

Системные свойства не совпадают с переменными окружения.

luis.espinal 06.10.2020 17:31

Вариант , основанный на ответе @ pushy's, работает в окнах.

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)

Ответ Тима Райана сработал для меня ... но я хотел, чтобы он был для 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|"

Версия на 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}")
}

Я наткнулся на эту ветку, поскольку у меня было аналогичное требование, когда мне нужно было установить (или обновить) переменную среды (навсегда).

Итак, я заглянул в - Как установить переменную среды постоянно из командной строки, и это было очень просто!

setx JAVA_LOC C:/Java/JDK

Затем я просто реализовал то же самое в своем коде Вот что я использовал (предположим, JAVA_LOC - это имя переменной окружения)

        String cmdCommand = "setx JAVA_LOC " + "C:/Java/JDK";
            ProcessBuilder processBuilder = new ProcessBuilder();
            processBuilder.command("cmd.exe", "/c", cmdCommand);
            processBuilder.start();

ProcessBuilder запускает cmd.exe и передает нужную команду. Переменная среды сохраняется, даже если вы завершаете работу JVM / перезагружаете систему, поскольку она не имеет никакого отношения к жизненному циклу JVM / программы.

Другие вопросы по теме