¿Cómo configuro las variables de entorno de Java?

289

¿Cómo configuro las variables de entorno de Java? Veo que puedo hacer esto para subprocesos usando ProcessBuilder. Sin embargo, tengo varios subprocesos para comenzar, así que prefiero modificar el entorno del proceso actual y dejar que los subprocesos lo hereden.

Hay una System.getenv(String)para obtener una sola variable de entorno. También puedo obtener una Mapdel conjunto completo de variables de entorno con System.getenv(). Pero, recurrir put()a eso Maparroja un UnsupportedOperationException- aparentemente significan que el medio ambiente es de solo lectura. Y no hay System.setenv().

Entonces, ¿hay alguna forma de establecer variables de entorno en el proceso actualmente en ejecución? ¿Si es así, cómo? Si no, ¿cuál es la razón? (¿Es porque esto es Java y, por lo tanto, no debería estar haciendo cosas obsoletas mal portables como tocar mi entorno?) Y si no es así, ¿alguna buena sugerencia para administrar los cambios de variables de entorno que voy a necesitar para alimentar a varios? subprocesos?

skiphoppy
fuente
System.getEnv () está diseñado para ser universal, algunos entornos ni siquiera tienen variables de entorno.
b1nary.atr0phy
77
Para cualquiera que lo necesite para un caso de uso de prueba de unidad: stackoverflow.com/questions/8168884/…
Atifm

Respuestas:

88

(¿Es porque se trata de Java y, por lo tanto, no debería estar haciendo cosas obsoletas mal portables como tocar mi entorno?)

Creo que has dado en el clavo.

Una posible forma de aliviar la carga sería factorizar un método

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

y pasar cualquier ProcessBuilder s antes de comenzar.

Además, probablemente ya lo sepas, pero puedes iniciar más de un proceso con el mismo ProcessBuilder. Entonces, si sus subprocesos son los mismos, no necesita hacer esta configuración una y otra vez.

Michael Myers
fuente
1
Es una pena que la gestión no me permita usar un lenguaje portátil diferente para ejecutar este conjunto de subprocesos malvados y obsoletos, entonces. :)
skiphoppy
18
S.Lott, no estoy buscando establecer el entorno de los padres. Estoy buscando establecer mi propio entorno.
skiphoppy
3
Eso funciona muy bien, a menos que sea la biblioteca de otra persona (por ejemplo, Sun) la que está iniciando el proceso.
sullivan-
24
@ b1naryatr0phy Te perdiste el punto. Nadie puede jugar con sus variables de entorno ya que esas variables son locales para un proceso (lo que configura en Windows son los valores predeterminados). Cada proceso es libre de cambiar sus propias variables ... a menos que sea Java.
maaartinus
99
Esta limitación de Java es un poco polémica. No hay ninguna razón para que java no le permita configurar entornos diferentes a "porque no queremos que java haga esto".
IanNorton
232

Para su uso en escenarios en los que necesita establecer valores de entorno específicos para pruebas unitarias, puede encontrar útil el siguiente truco. Cambiará las variables de entorno en toda la JVM (así que asegúrese de restablecer cualquier cambio después de su prueba), pero no alterará el entorno de su sistema.

Descubrí que una combinación de los dos hacks sucios de Edward Campbell y anónimo funciona mejor, ya que uno de ellos no funciona bajo Linux, uno no funciona bajo Windows 7. Entonces, para obtener un malvado truco multiplataforma, los combiné:

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

Esto funciona como un encanto. Créditos completos a los dos autores de estos hacks.

molesto
fuente
1
¿Esto solo cambiará en la memoria, o en realidad cambiará toda la variable de entorno en el sistema?
Shervin Asgari
36
Esto solo cambiará la variable de entorno en la memoria. Esto es bueno para las pruebas, ya que puede establecer la variable de entorno según sea necesario para su prueba, pero dejar las envueltas en el sistema tal como están. De hecho, desaconsejaría a cualquiera que use este código para cualquier otro propósito que no sea la prueba. Este código es malo ;-)
agresivo
99
Como FYI, la JVM crea una copia de las variables de entorno cuando se inicia. Esto editará esa copia, no las variables de entorno para el proceso padre que inició la JVM.
Bmeding
Intenté esto en Android y no pareció tomarlo. ¿Alguien más tiene suerte en Android?
Hans-Christoph Steiner
55
Seguro,import java.lang.reflect.Field;
agresivo
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);
        }
    }
}

O para agregar / actualizar una única var y eliminar el bucle según la sugerencia de 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
fuente
3
Parece que eso modificaría el mapa en la memoria, pero ¿guardaría el valor en el sistema?
Jon Onstott
1
bueno, sí cambia el mapa de memoria de las variables de entorno. Supongo que eso es suficiente en muchos casos de uso. @ Edward - ¡Dios mío, es difícil imaginar cómo se descubrió esta solución en primer lugar!
anirvan
13
Esto no cambiará las variables de entorno en el sistema, pero las cambiará en la invocación actual de Java. Esto es muy útil para pruebas unitarias.
Stuart K
10
¿Por qué no usar en Class<?> cl = env.getClass();lugar de eso para el bucle?
thejoshwolfe
1
¡Esto es exactamente lo que he estado buscando! He estado escribiendo pruebas de integración para algún código que utiliza una herramienta de terceros que, por alguna razón, solo le permite modificar su duración de tiempo de espera predeterminada absurdamente corta con una variable 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
fuente
17

en Android, la interfaz se expone a través de Libcore.os como una especie de API oculta.

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

La clase Libcore y el sistema operativo de la interfaz son públicos. Solo falta la declaración de clase y debe mostrarse al vinculador. No es necesario agregar las clases a la aplicación, pero tampoco hace daño si se incluye.

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;
}
usuario3404318
fuente
1
Probado y trabajando en Android 4.4.4 (CM11). PS El único ajuste que hice estaba reemplazando throws ErrnoExceptiona throws Exception.
DavisNT
77
API 21, tiene Os.setEnvahora. developer.android.com/reference/android/system/… , java.lang.String, boolean)
Jared Burrows
1
Potencialmente desaparecido ahora con las nuevas restricciones de Pie: developer.android.com/about/versions/pie/…
TWiStErRob
13

Solo Linux

Establecer variables de entorno individuales (basadas en la respuesta 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:

Primero, coloque el método en la clase que desee, por ejemplo, SystemUtil. Entonces llámalo estáticamente:

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

Si llamas System.getenv("SHELL")después de esto, volverás "/bin/bash".

Hubert Grzeskowiak
fuente
Lo anterior no funciona en Windows 10, pero será trabajar en Linux.
mengchengfeng
Interesante. No lo intenté yo mismo en Windows. ¿Recibes un error, @mengchengfeng?
Hubert Grzeskowiak
@HubertGrzeskowiak No vimos ningún mensaje de error, simplemente no funcionó ...
mengchengfeng
9

Esta es una combinación de la respuesta de @ paul-blair convertida a Java que incluye algunas limpiezas señaladas por paul blair y algunos errores que parecen haber estado dentro del código de @pushy que está compuesto por @Edward Campbell y anónimo.

No puedo enfatizar cuánto se debe usar SÓLO este código en las pruebas y es extremadamente hacky. Pero para los casos en que necesita la configuración del entorno en las pruebas, es exactamente lo que necesitaba.

Esto también incluye algunos pequeños toques míos que permiten que el código funcione tanto en Windows como en

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)

así como Centos corriendo en

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)

La implementación:

/**
 * 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));
}
hermano mayor
fuente
7

Resulta que la solución de @ pushy / @ anonymous / @ Edward Campbell no funciona en Android porque Android no es realmente Java. Específicamente, Android no tiene java.lang.ProcessEnvironmentnada. Pero resulta ser más fácil en Android, solo necesitas hacer una llamada JNI a POSIXsetenv() :

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

Y en 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
fuente
5

Como la mayoría de las personas que han encontrado este hilo, estaba escribiendo algunas pruebas unitarias y necesitaba modificar las variables de entorno para establecer las condiciones correctas para que se ejecutara la prueba. Sin embargo, descubrí que las respuestas más votadas tenían algunos problemas y / o eran muy crípticas o demasiado complicadas. Esperemos que esto ayude a otros a resolver la solución más rápidamente.

En primer lugar, finalmente encontré que la solución de @Hubert Grzeskowiak era la más simple y funcionó para mí. Desearía haber venido a eso primero. Se basa en la respuesta de @Edward Campbell, pero sin la complicación de la búsqueda en bucle.

Sin embargo, comencé con la solución de @ pushy, que obtuvo la mayor cantidad de votos. Es una combinación de @anonymous y @Edward Campbell's. @pushy afirma que ambos enfoques son necesarios para cubrir los entornos de Linux y Windows. Estoy ejecutando bajo OS X y encuentro que ambos funcionan (una vez que se soluciona un problema con el enfoque @anonymous). Como otros han señalado, esta solución funciona la mayor parte del tiempo, pero no todo.

Creo que la fuente de la mayor parte de la confusión proviene de la solución de @ anonymous que opera en el campo 'theEnvironment'. Mirando la definición de la estructura ProcessEnvironment , 'theEnvironment' no es un Map <String, String> sino un Map <Variable, Value>. Borrar el mapa funciona bien, pero la operación putAll reconstruye el mapa como Map <String, String>, lo que potencialmente causa problemas cuando las operaciones posteriores operan en la estructura de datos utilizando la API normal que espera Map <Variable, Value>. Además, acceder / eliminar elementos individuales es un problema. La solución es acceder indirectamente a 'theEnvironment' a través de 'theUnmodifiableEnvironment'. Pero como este es un tipo UnmodifiableMapel acceso debe hacerse a través de la variable privada 'm' del tipo UnmodifiableMap. Vea getModifiableEnvironmentMap2 en el código a continuación.

En mi caso, necesitaba eliminar algunas de las variables de entorno para mi prueba (las otras no deberían modificarse). Luego quise restaurar las variables de entorno a su estado anterior después de la prueba. Las rutinas a continuación lo hacen sencillo. Probé ambas versiones de getModifiableEnvironmentMap en OS X, y ambas funcionan de manera equivalente. Aunque basado en los comentarios en este hilo, uno puede ser una mejor opción que el otro dependiendo del entorno.

Nota: No incluí el acceso al 'theCaseInsensitiveEnvironmentField' ya que parece ser específico de Windows y no tenía forma de probarlo, pero agregarlo debería ser sencillo.

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
fuente
Gracias, fue exactamente mi caso de uso y también bajo Mac OS X.
Rafael Gonçalves
Me gustó tanto que creé una versión un poco más simple para Groovy, ver más abajo.
Mike roedor
4

Buscando en línea, parece que podría ser posible hacer esto con JNI. Entonces tendría que hacer una llamada a putenv () desde C, y (presumiblemente) tendría que hacerlo de una manera que funcionara tanto en Windows como en UNIX.

Si se puede hacer todo eso, seguramente no sería demasiado difícil para Java soportar esto en lugar de ponerme una camisa de fuerza.

Un amigo que habla Perl en otro lugar sugiere que esto se debe a que las variables de entorno son procesos globales y Java se esfuerza por un buen aislamiento para un buen diseño.

skiphoppy
fuente
Sí, puede configurar el entorno de procesos desde el código C. Pero no contaría con eso trabajando en Java. Existe una buena posibilidad de que la JVM copie el entorno en objetos de Java String durante el inicio, por lo que sus cambios no se utilizarán para futuras operaciones de JVM.
Darron
Gracias por la advertencia, Darron. Probablemente hay una buena posibilidad de que tengas razón.
skiphoppy
2
@Darron, muchas de las razones por las que uno quisiera hacer esto no tienen nada que ver con lo que la JVM cree que es el medio ambiente. (Piense en configurar LD_LIBRARY_PATHantes de llamar Runtime.loadLibrary(); la dlopen()llamada que invoca se ve en el entorno real , no en la idea de Java de lo mismo).
Charles Duffy
Esto funciona para subprocesos iniciados por una biblioteca nativa (que en mi caso es la mayoría de ellos), pero desafortunadamente no funciona para subprocesos iniciados por las clases Process o ProcessBuilder de Java.
Dan
4

Intenté la respuesta de Pushy arriba y funcionó en su mayor parte. Sin embargo, en ciertas circunstancias, vería esta excepción:

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

Esto sucede cuando se llama al método más de una vez, debido a la implementación de ciertas clases internas de ProcessEnvironment.Si el setEnv(..)método se llama más de una vez, cuando las claves se recuperan del theEnvironmentmapa, ahora son cadenas (después de haber sido colocadas en como cadenas por la primera invocación de setEnv(...)) y no se pueden convertir al tipo genérico del mapa,Variable, que es una clase interna privada deProcessEnvironment.

Una versión fija (en Scala), está debajo. Esperemos que no sea demasiado difícil de transferir a 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
fuente
¿Dónde se define JavaClass?
Mike Slinn
1
Es de suponer import java.lang.{Class => JavaClass}.
Randall Whitman
1
La implementación de java.lang.ProcessEnvironment es diferente en diferentes plataformas incluso para la misma compilación. Por ejemplo, no hay una clase java.lang.ProcessEnvironment $ Variable en la implementación de Windows, pero esta clase existe en una para Linux. Puedes verificarlo fácilmente. Simplemente descargue la distribución tar.gz JDK para Linux y extraiga la fuente de src.zip y luego compárela con el mismo archivo de la distribución para Windows. Son totalmente diferentes en JDK 1.8.0_181. No los he comprobado en Java 10, pero no me sorprenderá si hay la misma imagen.
Alex Konshin
1

Esta es la versión malvada de Kotlin de la respuesta malvada 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 en macOS Mojave al menos.

GarouDan
fuente
0

Si trabaja con SpringBoot, puede agregar especificando la variable ambiental en la siguiente propiedad:

was.app.config.properties.toSystemProperties
Alex
fuente
1
¿Puedes por favor explicar un poco?
Faraz
0

variante basada en la respuesta de @ pushy , funciona en 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
fuente
0

La respuesta de Tim Ryan funcionó para mí ... pero la quería para Groovy (por ejemplo, el contexto de Spock) y 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|"
Mike roedor
fuente
0

Una versión en Kotlin, en este algoritmo, creé un decorador que le permite establecer y obtener variables del entorno.

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
fuente
-1

Implementación de Kotlin que hice recientemente en base a la respuesta 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
fuente
-12

Puede pasar parámetros a su proceso inicial de Java con -D:

java -cp <classpath> -Dkey1=value -Dkey2=value ...
mate b
fuente
Los valores no se conocen en tiempo de ejecución; se conocen durante la ejecución del programa cuando el usuario los proporciona / selecciona. Y eso establece solo las propiedades del sistema, no las variables de entorno.
skiphoppy
Entonces, en ese caso, probablemente desee encontrar una forma regular (a través del parámetro args [] para el método principal) para invocar sus subprocesos.
mate b
Matt B, la forma habitual es a través de ProcessBuilder, como se menciona en mi pregunta original. :)
skiphoppy
77
-D parámetros están disponibles a través System.getPropertyy no son los mismos que System.getenv. Además, la Systemclase también permite establecer estas propiedades estáticamente usandosetProperty
anirvan