Pergunta

Como faço para definir variáveis ??de ambiente de Java? Eu vejo que eu posso fazer isso por subprocessos usando ProcessBuilder . Tenho vários subprocessos para começar, embora, por isso eu prefiro modificar o ambiente do processo atual e deixar os subprocessos herdá-lo.

Há uma System.getenv(String) para obter uma única variável de ambiente. Eu também pode obter um Map do conjunto completo de variáveis ??de ambiente com System.getenv(). Mas, chamando put() em que Map lança uma UnsupportedOperationException - aparentemente eles significam para o ambiente a ser somente leitura. E, não há System.setenv().

Assim, há alguma maneira de variáveis ??de ambiente definidas no processo atualmente em execução? Se sim, como? Se não, qual é a razão? (Será que é porque este é Java e, portanto, eu não deveria estar fazendo mal nonportable coisas obsoletas, como tocar meu ambiente?) E se não, alguma sugestão boa para a gestão do ambiente variáveis ??mudanças que eu vou precisar de estar alimentando a vários subprocessos?

Foi útil?

Solução

(Será que é porque este é Java e, portanto, eu não deveria estar fazendo mal nonportable coisas obsoletas como tocar meu ambiente?)

Eu acho que você bateu o prego na cabeça.

Uma maneira possível para aliviar a carga seria fator um método

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

e passar quaisquer ProcessBuilders através dele antes de iniciar-los.

Além disso, você provavelmente já sabe disso, mas você pode começar mais de um processo com o mesmo ProcessBuilder. Portanto, se seus subprocessos são os mesmos, você não precisa fazer esta configuração mais e mais.

Outras dicas

Para uso em situações em que você precisa para valores de ambiente conjunto específico de testes de unidade, você pode encontrar o seguinte truque útil. Ela vai mudar as variáveis ??de ambiente em todo o JVM (para ter certeza de repor quaisquer alterações após o teste), mas não irá alterar o ambiente do sistema.

Eu achei que uma combinação dos dois hacks sujos por Edward Campbell e anônimo funciona melhor, como um dos não funciona no linux, não funciona no Windows 7. Assim, para obter um mal multiplataforma cortar Eu combinei-los:

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 totais para os dois autores desses hacks.

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 um único var e remover o loop como por sugestão thejoshwolfe do.

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

no Android a interface é exposta através Libcore.os como uma espécie de API escondido.

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

A classe Libcore, bem como o sistema operacional interface é público. Apenas a declaração da classe está faltando e precisa ser mostrado para o vinculador. Não há necessidade de adicionar as classes da aplicação, mas também não faz mal se for 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;
}

O Linux só

Definir variáveis ??de ambiente de solteiro (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:

Em primeiro lugar, colocar o método em qualquer classe que você deseja, por exemplo, SystemUtil.

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

Se você chamar System.getenv("SHELL") depois disto, você terá "/bin/bash" de volta.

Acontece que a solução de @ agressivo / @ anônima / @ Edward Campbell não funciona no Android porque o Android não é realmente Java. Especificamente, o Android não tem java.lang.ProcessEnvironment em tudo. Mas acaba por ser mais fácil em 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);
    }
}

Esta é uma combinação de @ paul-blair 'resposta s convertidos em Java que inclui algumas limpezas apontado por Paul Blair e alguns erros que parecem ter sido dentro @pushy' código s, que é composta de @Edward Campbell e anônimo.

Eu não posso enfatizar o quanto este código só deve ser usado em testes e é extremamente hacky. Mas para casos em que você precisa a configuração do ambiente em testes é exatamente o que eu precisava.

Isto também inclui alguns toques menores de mina que permitem que o código para trabalhar 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)

, bem como Centos execução no

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

bisbilhotando on-line, parece que ele poderia ser possível fazer isso com JNI. Você teria, então, para fazer uma chamada para putenv () de C, e você (presumivelmente) tem que fazer isso de uma forma que funcionou em Windows e UNIX.

Se tudo o que pode ser feito, ele certamente não seria muito difícil para si Java para apoiar este em vez de me colocar em uma camisa de força.

amigo de língua Perl Um outro lugar sugere que isso acontece porque as variáveis ??de ambiente são processo global e Java está se esforçando para um bom isolamento para o bom design.

A resposta de insistente Tentei acima e funcionou para a maior parte. No entanto, em certas circunstâncias, gostaria de ver essa exceção:

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

Esta acaba por acontecer quando o método foi chamado mais de uma vez, devido à aplicação de determinadas classes internas de ProcessEnvironment. Se o método setEnv(..) é chamado mais de uma vez, quando as chaves são recuperados a partir do mapa theEnvironment, são agora strings (tendo sido colocado em como cordas pela primeira invocação de setEnv(...)) e não pode ser convertido para tipo genérico do mapa, Variable, que é uma classe interna privada de ProcessEnvironment.

A versão fixa (em Scala), está abaixo. Esperemos que não é muito difícil de transitar em 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()
  }
}

Como a maioria das pessoas que encontraram esta discussão, eu estava escrevendo alguns testes de unidade e necessária para modificar as variáveis ??de ambiente para definir as condições corretas para o teste a ser executado. No entanto, eu encontrei as respostas mais upvoted teve alguns problemas e / ou eram muito críptica ou excessivamente complicado. Esperemos que isso vai ajudar os outros a resolver a solução mais rapidamente.

Primeiro, eu finalmente encontrei a solução da @Hubert Grzeskowiak para ser o mais simples e funcionou para mim. Eu gostaria de ter chegado a esse primeiro. É baseado na resposta de @Edward Campbell, mas sem o complicador para a busca loop.

No entanto, eu comecei com @ do insistente solução, que tem o maior número de upvotes. É uma combinação de @anonymous e @Edward Campbell. reivindicações @pushy ambas as abordagens são necessários para cobrir ambos os ambientes Linux e Windows. Estou executando sob OS X e achar que tanto o trabalho (uma vez um problema com a abordagem @anonymous é fixo). Como outros já mencionado, esta solução funciona na maioria das vezes, mas não todos.

Eu acho que a fonte da maior parte da confusão vem de @ operacional solução de anônimo na 'theenvironment' campo. Olhando para a definição do estrutura ProcessEnvironment , 'theenvironment' não é um Map , mas sim que é um Map . Limpar o mapa fina funciona, mas a operação putAll reconstrói a mapear um Map , o que potencialmente causa problemas quando as operações subsequentes operar sobre a estrutura de dados usando a API normal que espera Map . Além disso, o acesso / remoção de elementos individuais é um problema. A solução é o acesso 'theenvironment' indiretamente por meio de 'theUnmodifiableEnvironment'. Mas uma vez que este é um tipo UnmodifiableMap o acesso deve ser feito através da variável privada 'm' do tipo UnmodifiableMap. Veja getModifiableEnvironmentMap2 no código abaixo.

No meu caso eu precisava para remover algumas das variáveis ??de ambiente para o meu teste (os outros deve ser inalterado). Então eu queria restaurar as variáveis ??de ambiente ao seu estado anterior após o teste. As rotinas abaixo make que a frente para fazer. Eu testei as duas versões do getModifiableEnvironmentMap no OS X, e ambos trabalham de forma equivalente. Embora com base em comentários neste tópico, pode ser uma escolha melhor do que o outro, dependendo do ambiente.

Nota: Eu não incluem acesso ao 'theCaseInsensitiveEnvironmentField', uma vez que parece ser o Windows específico e eu não tinha nenhuma maneira de testá-lo, mas acrescentando que deveria ser para a frente

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

Esta é a versão do mal Kotlin de resposta mal o @ do insistente =)

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

Ele está trabalhando no MacOS Mojave, pelo menos.

implementação Kotlin Eu 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)
    }
}

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

java -cp <classpath> -Dkey1=value -Dkey2=value ...
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top