Como faço para definir variáveis ​​de ambiente do Java?

289

Como faço para definir variáveis ​​de ambiente do Java? Vejo que posso fazer isso para subprocessos usando ProcessBuilder. No entanto, tenho vários subprocessos para iniciar, então prefiro modificar o ambiente do processo atual e deixar que os subprocessos o herdem.

Existe um System.getenv(String)para obter uma única variável de ambiente. Também posso obter um Mapconjunto completo de variáveis ​​de ambiente com System.getenv(). Mas, chamando put()isso Maplança um UnsupportedOperationException- aparentemente eles significam que o ambiente seja apenas para leitura. E não há System.setenv().

Então, existe alguma maneira de definir variáveis ​​de ambiente no processo em execução no momento? Se sim, como? Se não, qual é a lógica? (É por ser Java e, portanto, eu não deveria estar fazendo coisas obsoletas não-portáveis ​​como tocar meu ambiente?) E, se não, existem boas sugestões para gerenciar as variáveis ​​de ambiente que eu precisarei alimentar várias subprocessos?

skiphoppy
fonte
System.getEnv () deve ser universal, alguns ambientes nem possuem variáveis ​​de ambiente.
B1nary.atr0phy
7
Para quem precisou disso para um caso de uso de teste de unidade: stackoverflow.com/questions/8168884/…
Atifm
Para o Scala, use o seguinte: gist.github.com/vpatryshev/b1bbd15e2b759c157b58b68c58891ff4
Vlad Patryshev

Respostas:

88

(É por ser Java e, portanto, eu não deveria estar fazendo coisas obsoletas não-portáveis ​​como tocar meu ambiente?)

Eu acho que você acertou a unha na cabeça.

Uma maneira possível de aliviar o fardo seria fatorar um método

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

e passe todos os ProcessBuilders antes de iniciá-los.

Além disso, você provavelmente já sabe disso, mas pode iniciar mais de um processo com o mesmo ProcessBuilder. Portanto, se seus subprocessos forem os mesmos, você não precisará fazer essa configuração repetidamente.

Michael Myers
fonte
1
É uma pena que o gerenciamento não me permita usar uma linguagem portátil diferente para executar esse conjunto de subprocessos obsoletos e maus. :) #
314 skiphoppy
18
S.Lott, não estou procurando definir o ambiente dos pais. Estou procurando definir meu próprio ambiente.
Skiphoppy
3
Isso funciona muito bem, a menos que seja a biblioteca de outra pessoa (por exemplo, a Sun) que está iniciando o processo.
precisa saber é o seguinte
24
@ b1naryatr0phy Você errou o ponto. Ninguém pode brincar com suas variáveis ​​de ambiente, pois essas variáveis ​​são locais para um processo (o que você define no Windows são os valores padrão). Cada processo é livre para alterar suas próprias variáveis ​​... a menos que seja Java.
Maaartinus
9
Essa limitação do java é um pouco fora de controle. Não há razão para o java não permitir que você defina outros envios além de "porque não queremos que o java faça isso".
21917 IanNorton
232

Para uso em cenários em que você precisa definir valores de ambiente específicos para testes de unidade, você pode achar útil o seguinte hack. Ele alterará as variáveis ​​de ambiente em toda a JVM (certifique-se de redefinir as alterações após o teste), mas não alterará o ambiente do sistema.

Eu descobri que uma combinação dos dois hacks sujos de Edward Campbell e anônimos funciona melhor, pois um deles não funciona no linux, o computador não funciona no windows 7. Então, para obter um truque maligno multiplataforma, eu os combinei:

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

Isso funciona como um encanto. Créditos completos para os dois autores desses hacks.

insistente
fonte
1
Isso mudará apenas na memória ou realmente alterará toda a variável de ambiente no sistema?
Shervin Asgari
36
Isso mudará apenas a variável de ambiente na memória. Isso é bom para teste, porque você pode definir a variável de ambiente conforme necessário para o teste, mas deixar os envs no sistema como estão. De fato, eu desencorajaria fortemente qualquer pessoa a usar esse código para qualquer outro fim que não o teste. Este código é mal ;-)
insistente
9
Como FYI, a JVM cria uma cópia das variáveis ​​de ambiente quando é iniciada. Isso editará essa cópia, não as variáveis ​​de ambiente do processo pai que iniciou a JVM.
bmeding
Eu tentei isso no Android e não parecia demorar. Mais alguém tem sorte no Android?
Hans-Christoph Steiner
5
Claro,import java.lang.reflect.Field;
insistente
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);
        }
    }
}

Ou para adicionar / atualizar uma única var e remover o loop de acordo com a sugestão do 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
fonte
3
Parece que isso modificaria o mapa na memória, mas salvaria o valor no sistema?
Jon Onstott
1
bem, ele altera o mapa de memória das variáveis ​​de ambiente. Eu acho que isso basta em muitos casos de uso. @ Edward - nossa, é difícil imaginar como essa solução foi descoberta em primeiro lugar!
Anirvan
13
Isso não altera as variáveis ​​de ambiente no sistema, mas as altera na chamada atual do Java. Isso é muito útil para testes de unidade.
Stuart K
10
por que não usar em Class<?> cl = env.getClass();vez disso para loop?
Thejoshwolfe
1
É exatamente isso que eu tenho procurado! Escrevi testes de integração para algum código que usa uma ferramenta de terceiros que, por algum motivo, apenas permite modificar seu tamanho absurdamente curto do tempo limite padrão com uma variável ambiental.
David DeMar
21
// this is a dirty hack - but should be ok for a unittest.
private void setNewEnvironmentHack(Map<String, String> newenv) throws Exception
{
  Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
  Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
  theEnvironmentField.setAccessible(true);
  Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
  env.clear();
  env.putAll(newenv);
  Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
  theCaseInsensitiveEnvironmentField.setAccessible(true);
  Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
  cienv.clear();
  cienv.putAll(newenv);
}
anônimo
fonte
17

no Android, a interface é exposta via Libcore.os como um tipo de API oculta.

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

A classe Libcore e o SO da interface são públicos. Apenas a declaração de classe está ausente e precisa ser mostrada ao vinculador. Não há necessidade de adicionar as classes ao aplicativo, mas também não prejudica se estiver incluído.

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
fonte
1
Testado e funcionando no Android 4.4.4 (CM11). PS O único ajuste que eu fiz foi substituir throws ErrnoExceptioncom throws Exception.
DavisNT
7
API 21, tem Os.setEnvagora. developer.android.com/reference/android/system/... , java.lang.String, boolean)
Jared Burrows
1
Potencialmente extinto agora com as novas restrições de Pie: developer.android.com/about/versions/pie/…
TWiStErRob
13

Apenas Linux

Configurando variáveis ​​de ambiente únicas (com base na resposta de Edward Campbell):

public static void setEnv(String key, String value) {
    try {
        Map<String, String> env = System.getenv();
        Class<?> cl = env.getClass();
        Field field = cl.getDeclaredField("m");
        field.setAccessible(true);
        Map<String, String> writableEnv = (Map<String, String>) field.get(env);
        writableEnv.put(key, value);
    } catch (Exception e) {
        throw new IllegalStateException("Failed to set environment variable", e);
    }
}

Uso:

Primeiro, coloque o método em qualquer classe que você desejar, por exemplo, SystemUtil. Em seguida, chame-o estaticamente:

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

Se você ligar System.getenv("SHELL")depois disso, "/bin/bash"retornará.

Hubert Grzeskowiak
fonte
O acima não funciona no Windows 10, mas vai trabalhar em linux.
mengchengfeng 25/09
Interessante. Eu não tentei eu mesmo no Windows. Você recebeu um erro, @mengchengfeng?
Hubert Grzeskowiak 26/09
@HubertGrzeskowiak Nós não ver quaisquer mensagens de erro, ele simplesmente não funciona ...
mengchengfeng
9

Essa é uma combinação da resposta de @ paul-blair convertida em Java, que inclui algumas limpezas apontadas por paul blair e alguns erros que parecem estar dentro do código do @pushy, que é composto por @Edward Campbell e anônimo.

Não posso enfatizar o quanto esse código SOMENTE deve ser usado nos testes e é extremamente hacky. Mas nos casos em que você precisa da configuração do ambiente em testes, é exatamente o que eu precisava.

Isso também inclui alguns pequenos toques meus que permitem que o código funcione no Windows em execução no

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)

assim como o Centos rodando em

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)

A implementação:

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

Acontece que a solução de @ pushy / @ anonymous / @ Edward Campbell não funciona no Android porque o Android não é realmente Java. Especificamente, o Android não tem java.lang.ProcessEnvironment. Mas acaba sendo mais fácil no Android, você só precisa fazer uma chamada JNI para POSIX setenv():

Em C / JNI:

JNIEXPORT jint JNICALL Java_com_example_posixtest_Posix_setenv
  (JNIEnv* env, jclass clazz, jstring key, jstring value, jboolean overwrite)
{
    char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL);
    char* v = (char *) (*env)->GetStringUTFChars(env, value, NULL);
    int err = setenv(k, v, overwrite);
    (*env)->ReleaseStringUTFChars(env, key, k);
    (*env)->ReleaseStringUTFChars(env, value, v);
    return err;
}

E em 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);
    }
}
Hans-Christoph Steiner
fonte
5

Como a maioria das pessoas que encontrou esse encadeamento, eu estava escrevendo alguns testes de unidade e precisava modificar as variáveis ​​de ambiente para definir as condições corretas para a execução do teste. No entanto, descobri que as respostas mais votadas tinham alguns problemas e / ou eram muito enigmáticas ou excessivamente complicadas. Espero que isso ajude outras pessoas a resolver a solução mais rapidamente.

Primeiro, finalmente achei a solução do @Hubert Grzeskowiak a mais simples e funcionou para mim. Eu gostaria de ter chegado a esse primeiro. É baseado na resposta de @Edward Campbell, mas sem a complicada pesquisa de loop.

No entanto, comecei com a solução @ pushy, que obteve o maior número de votos. É uma combinação de @anonymous e @Edward Campbell's. A @pushy afirma que as duas abordagens são necessárias para cobrir os ambientes Linux e Windows. Estou executando no OS X e acho que ambos funcionam (uma vez que um problema com a abordagem anônima é corrigido). Como outros observaram, essa solução funciona na maioria das vezes, mas não em todas.

Acho que a fonte da maior parte da confusão vem da solução da @ anonymous operando no campo 'theEnvironment'. Observando a definição da estrutura ProcessEnvironment , 'theEnvironment' não é um Mapa <String, String>, mas sim um Mapa <Variável, Valor>. A limpeza do mapa funciona bem, mas a operação putAll reconstrói o mapa como Map <String, String>, o que potencialmente causa problemas quando operações subsequentes operam na estrutura de dados usando a API normal que espera Map <Variable, Value>. Além disso, acessar / remover elementos individuais é um problema. A solução é acessar indiretamente o 'ambiente' através do 'ambiente não modificável'. Mas como esse é um tipo UnmodifiableMapo acesso deve ser feito através da variável privada 'm' do tipo UnmodifiableMap. Veja getModifiableEnvironmentMap2 no código abaixo.

No meu caso, eu precisei remover algumas das variáveis ​​de ambiente para o meu teste (as outras devem permanecer inalteradas). Depois, quis restaurar as variáveis ​​de ambiente ao seu estado anterior após o teste. As rotinas abaixo tornam isso fácil de fazer. Testei as duas versões do getModifiableEnvironmentMap no OS X e as duas funcionam de maneira equivalente. Embora com base nos comentários deste tópico, uma pode ser uma escolha melhor que a outra, dependendo do ambiente.

Nota: eu não incluí o acesso ao 'theCaseInsensitiveEnvironmentField', pois parece ser específico do Windows e não tinha como testá-lo, mas a adição deve ser direta.

private Map<String, String> getModifiableEnvironmentMap() {
    try {
        Map<String,String> unmodifiableEnv = System.getenv();
        Class<?> cl = unmodifiableEnv.getClass();
        Field field = cl.getDeclaredField("m");
        field.setAccessible(true);
        Map<String,String> modifiableEnv = (Map<String,String>) field.get(unmodifiableEnv);
        return modifiableEnv;
    } catch(Exception e) {
        throw new RuntimeException("Unable to access writable environment variable map.");
    }
}

private Map<String, String> getModifiableEnvironmentMap2() {
    try {
        Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
        Field theUnmodifiableEnvironmentField = processEnvironmentClass.getDeclaredField("theUnmodifiableEnvironment");
        theUnmodifiableEnvironmentField.setAccessible(true);
        Map<String,String> theUnmodifiableEnvironment = (Map<String,String>)theUnmodifiableEnvironmentField.get(null);

        Class<?> theUnmodifiableEnvironmentClass = theUnmodifiableEnvironment.getClass();
        Field theModifiableEnvField = theUnmodifiableEnvironmentClass.getDeclaredField("m");
        theModifiableEnvField.setAccessible(true);
        Map<String,String> modifiableEnv = (Map<String,String>) theModifiableEnvField.get(theUnmodifiableEnvironment);
        return modifiableEnv;
    } catch(Exception e) {
        throw new RuntimeException("Unable to access writable environment variable map.");
    }
}

private Map<String, String> clearEnvironmentVars(String[] keys) {

    Map<String,String> modifiableEnv = getModifiableEnvironmentMap();

    HashMap<String, String> savedVals = new HashMap<String, String>();

    for(String k : keys) {
        String val = modifiableEnv.remove(k);
        if (val != null) { savedVals.put(k, val); }
    }
    return savedVals;
}

private void setEnvironmentVars(Map<String, String> varMap) {
    getModifiableEnvironmentMap().putAll(varMap);   
}

@Test
public void myTest() {
    String[] keys = { "key1", "key2", "key3" };
    Map<String, String> savedVars = clearEnvironmentVars(keys);

    // do test

    setEnvironmentVars(savedVars);
}
Tim Ryan
fonte
Obrigado, foi exatamente o meu caso de uso e também no mac os x.
Rafael Gonçalves
Gostava tanto disso que eu criei uma versão um pouco mais simples para o Groovy, veja abaixo.
mike roedor
4

Procurando on-line, parece que é possível fazer isso com o JNI. Você teria que fazer uma chamada para putenv () de C e (presumivelmente) teria que fazê-lo de uma maneira que funcionasse no Windows e no UNIX.

Se tudo isso puder ser feito, certamente não seria muito difícil para o próprio Java suportar isso em vez de me colocar em uma camisa de força.

Um amigo que fala Perl em outro lugar sugere que isso ocorre porque as variáveis ​​de ambiente são globais do processo e o Java está buscando um bom isolamento para um bom design.

skiphoppy
fonte
Sim, você pode definir o ambiente de processos a partir do código C. Mas eu não contaria com isso trabalhando em Java. Há uma boa chance de que a JVM copie o ambiente nos objetos Java String durante a inicialização, para que suas alterações não sejam usadas para operações futuras da JVM.
Darron
Obrigado pelo aviso, Darron. Provavelmente há uma boa chance de você estar certo.
Skiphoppy
2
@Darron Muitas das razões pelas quais alguém gostaria de fazer isso não têm nada a ver com o que a JVM pensa que é o ambiente. (Pense em definir LD_LIBRARY_PATHantes de chamar Runtime.loadLibrary(); a dlopen()chamada que ele chama olha para o ambiente real , não para a idéia de Java sobre o mesmo).
Charles Duffy
Isso funciona para subprocessos iniciados por uma biblioteca nativa (que no meu caso é a maioria deles), mas infelizmente não funciona para subprocessos iniciados pelas classes Process ou ProcessBuilder do Java.
Dan
4

Tentei responder pushy acima e funcionou na maior parte. No entanto, em certas circunstâncias, eu veria essa exceção:

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

Isso acontece quando o método foi chamado mais de uma vez, devido à implementação de certas classes internas de ProcessEnvironment.Se o setEnv(..)método é chamado mais de uma vez, quando as chaves são recuperadas do theEnvironmentmapa, elas agora são cadeias (sendo inseridas em como strings pela primeira invocação de setEnv(...)) e não pode ser convertida no tipo genérico do mapa, Variable,que é uma classe interna privada deProcessEnvironment.

Uma versão fixa (em Scala) está abaixo. Espero que não seja muito difícil transferir para 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()
  }
}
Paul Blair
fonte
Onde o JavaClass é definido?
Mike Slinn
1
Presumivelmente import java.lang.{Class => JavaClass}.
Randall Whitman
1
A implementação de java.lang.ProcessEnvironment é diferente em plataformas diferentes, mesmo para a mesma construção. Por exemplo, não há classe java.lang.ProcessEnvironment $ Variable na implementação do Windows, mas essa classe existe em uma para Linux. Você pode verificar facilmente. Basta baixar a distribuição tar.gz JDK para Linux e extrair a fonte do src.zip e compará-la com o mesmo arquivo da distribuição do Windows. Eles são totalmente diferentes no JDK 1.8.0_181. Eu não os verifiquei no Java 10, mas não ficarei surpreso se houver a mesma imagem.
Alex Konshin
1

Esta é a versão má de Kotlin da resposta má de @ 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)
            }
        }
    }

Está funcionando no macOS Mojave pelo menos.

GarouDan
fonte
0

Se você trabalha com o SpringBoot, pode adicionar a especificação da variável ambiental na seguinte propriedade:

was.app.config.properties.toSystemProperties
Alex
fonte
1
Você pode explicar um pouco?
Faraz 17/03
0

variante baseada na resposta de @ pushy , funciona no Windows.

def set_env(newenv):
    from java.lang import Class
    process_environment = Class.forName("java.lang.ProcessEnvironment")
    environment_field =  process_environment.getDeclaredField("theEnvironment")
    environment_field.setAccessible(True)
    env = environment_field.get(None)
    env.putAll(newenv)
    invariant_environment_field = process_environment.getDeclaredField("theCaseInsensitiveEnvironment");
    invariant_environment_field.setAccessible(True)
    invevn = invariant_environment_field.get(None)
    invevn.putAll(newenv)

Uso:

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

A resposta de Tim Ryan funcionou para mim ... mas eu a queria para Groovy (contexto de Spock, por exemplo) e simplissimo:

import java.lang.reflect.Field

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

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

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

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

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

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

Uma versão no Kotlin, neste algoritmo, criei um decorador que permite definir e obter variáveis ​​do ambiente.

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

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

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

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

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

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

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

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

    println("Java Home: ${knowProps.JAVA_HOME}")
    println("Sample: ${knowProps.sample}")
}
Tiarê Balbi
fonte
-1

Implementação de Kotlin que fiz recentemente com base na resposta de Edward:

fun setEnv(newEnv: Map<String, String>) {
    val unmodifiableMapClass = Collections.unmodifiableMap<Any, Any>(mapOf()).javaClass
    with(unmodifiableMapClass.getDeclaredField("m")) {
        isAccessible = true
        @Suppress("UNCHECKED_CAST")
        get(System.getenv()) as MutableMap<String, String>
    }.apply {
        clear()
        putAll(newEnv)
    }
}
Rik
fonte
-12

Você pode passar parâmetros para seu processo java inicial com -D:

java -cp <classpath> -Dkey1=value -Dkey2=value ...
matt b
fonte
Os valores não são conhecidos no tempo de execução; eles se tornam conhecidos durante a execução do programa quando o usuário os fornece / os seleciona. E isso define apenas propriedades do sistema, não variáveis ​​de ambiente.
Skiphoppy
Então, nesse caso, você provavelmente deseja encontrar uma maneira regular (através do parâmetro args [] para o método principal) para invocar seus subprocessos.
mate b
matt b, a maneira regular é via ProcessBuilder, como mencionado na minha pergunta original. :)
skiphoppy
7
Os parâmetros -D estão disponíveis System.getPropertye não são os mesmos que System.getenv. Além disso, a Systemclasse também permite definir essas propriedades estaticamente usandosetProperty
Anirvan