¿Cómo configuro variables de entorno desde Java?
¿Cómo configuro variables de entorno desde Java? Veo que puedo hacer esto para subprocesos usando ProcessBuilder
. Sin embargo, tengo varios subprocesos para iniciar, por lo que prefiero modificar el entorno del proceso actual y dejar que los subprocesos lo hereden.
Existe una opción System.getenv(String)
para obtener una única variable de entorno. También puedo obtener una parte Map
del conjunto completo de variables de entorno con System.getenv()
. Pero, put()
al invocar eso Map
se genera un UnsupportedOperationException
... aparentemente quieren decir que el entorno sea de solo lectura. Y no hay System.setenv()
.
Entonces, ¿hay alguna forma de establecer variables de entorno en el proceso que se está ejecutando actualmente? ¿Si es así, cómo? Si no, ¿cuál es el motivo? (¿Es porque esto es Java y, por lo tanto, no debería estar haciendo cosas obsoletas no portátiles como tocar mi entorno?) Y si no, ¿alguna buena sugerencia para gestionar los cambios en las variables de entorno que tendré que alimentar a varios subprocesos?
Para usar en escenarios donde necesita establecer valores de entorno específicos para pruebas unitarias, el siguiente truco puede resultarle útil. Cambiará las variables de entorno en toda la JVM (así que asegúrese de restablecer cualquier cambio después de la prueba), pero no alterará el entorno de su sistema.
Descubrí que una combinación de los dos trucos sucios de Edward Campbell y anónimo funciona mejor, ya que uno no funciona en Linux y el otro no funciona en Windows 7. Entonces, para obtener un truco malvado 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 de maravilla. Créditos completos a los dos autores de estos trucos.
(¿Es porque esto es Java y, por lo tanto, no debería estar haciendo cosas malvadas y obsoletas, no portátiles, como tocar mi entorno?)
Creo que has dado en el clavo.
Una posible forma de aliviar la carga sería descartar un método
void setUpEnvironment(ProcessBuilder builder) {
Map<String, String> env = builder.environment();
// blah blah
}
y pase cualquier ProcessBuilder
mensaje a través de él antes de iniciarlo.
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 realizar esta configuración una y otra vez.
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 agregar/actualizar una sola 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);
}
Establecer variables de entorno únicas (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 cualquier clase que desee, por ejemplo, SystemUtil. Luego llámalo estáticamente:
SystemUtil.setEnv("SHELL", "/bin/bash");
Si llamas System.getenv("SHELL")
después de esto, volverás "/bin/bash"
.