Wie kann ich Umgebungsvariablen aus Java?
-
11-07-2019 - |
Frage
Wie kann ich Umgebungsvariablen aus Java? Ich sehe, dass ich dies für Teilprozesse tun kann mit ProcessBuilder
. Ich habe mehrere Teilprozesse zu starten, obwohl, so würde ich eher den aktuellen Prozess Umgebung ändern und lassen Sie die Subprozesse es erben.
Es gibt eine System.getenv(String)
für eine einzige Umgebungsvariable zu bekommen. Ich kann auch eine Map
des vollständigen Satzes von Umgebungsvariablen mit System.getenv()
erhalten. Aber an diesem put()
Map
Aufruf wirft einen UnsupportedOperationException
- anscheinend bedeuten sie für die Umwelt nur zu lesen. Und es gibt keinen System.setenv()
.
So ist es eine Möglichkeit, Umgebungsvariablen im laufenden Prozess zu setzen? Wenn das so ist, wie? Wenn nicht, was ist der Grund? (Ist es, weil diese Java ist und deshalb soll ich nicht meine Umgebung wie zu berühren, Böse zu tun nonportable veraltete Dinge sein?) Und wenn nicht, gute Vorschläge für die Verwaltung der Umgebungsvariable ändert, dass ich gehen zu müssen, um mehr zu füttern Subprozesse?
Lösung
(Ist es, weil diese Java ist und deshalb sollte ich nicht meine Umgebung wie zu berühren, Böses zu tun nonportable veraltete Dinge werden?)
Ich glaube, Sie den Nagel auf den Kopf getroffen haben.
Ein möglicher Weg, um die Last zu erleichtern würde, ein Verfahren zur ausklammern
void setUpEnvironment(ProcessBuilder builder) {
Map<String, String> env = builder.environment();
// blah blah
}
und übergeben alle ProcessBuilder
s durch bevor sie beginnen.
Auch Sie wahrscheinlich bereits wissen, aber Sie können mit dem gleichen ProcessBuilder
mehr als einen Prozess starten. Also, wenn Ihr Subprozesse die gleichen sind, die Sie nicht brauchen diese Einstellung über und über zu tun.
Andere Tipps
Für den Einsatz in Szenarien, in denen Sie bestimmte Umgebungswerte für Unit-Tests festlegen müssen, können Sie den folgenden Hack nützlich finden. Es wird die Umgebungsvariablen in der gesamten JVM ändern (so stellen Sie sicher, dass Sie alle Änderungen nach dem Test Reset), wird aber nicht Ihre Systemumgebung verändern.
Ich fand, dass eine Kombination der beiden schmutzigen Hacks von Edward Campbell und anonym am besten funktioniert, als einer der nicht unter Linux funktioniert, muss man nicht 7 unter Windows arbeiten, so ein Multi-Plattform bösen Hack, den ich ihnen kombiniert zu bekommen:
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);
}
}
}
}
Dies funktioniert wie ein Zauber. Voll Kredite an den beiden Autoren dieser 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);
}
}
}
oder hinzuzufügen / Aktualisieren eines einzelnen var und Entfernen der Schleife gemäß thejoshwolfe Vorschlag.
@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);
}
auf Android die Schnittstelle über Libcore.os als eine Art versteckter API ausgesetzt ist.
Libcore.os.setenv("VAR", "value", bOverwrite);
Libcore.os.getenv("VAR"));
Die libcore Klasse sowie die Schnittstelle OS ist öffentlich. Nur die Klassendeklaration fehlt und müssen an den Linker zu zeigen. Keine Notwendigkeit, die Klassen der Anwendung hinzuzufügen, aber es ist auch nicht schaden, wenn es enthalten ist.
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;
}
Linux nur
Einstellen einzelne Umgebungsvariablen (basierend auf Antwort von 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);
}
}
Verwendung:
Zuerst legen Sie die Methode in jeder Klasse, die Sie wollen, zum Beispiel SystemUtil.
SystemUtil.setEnv("SHELL", "/bin/bash");
Wenn Sie System.getenv("SHELL")
nach diesem Aufruf, werden Sie "/bin/bash"
zurück.
Es stellt sich heraus, dass die Lösung von @ aufdringlich / @ anonym / @ Edward Campbell auf Android funktioniert nicht, weil Android nicht wirklich Java ist. Insbesondere hat Android nicht java.lang.ProcessEnvironment
überhaupt. Aber es stellt sich heraus, in Android einfacher zu sein, brauchen Sie nur einen JNI Aufruf POSIX setenv()
zu tun:
In 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;
}
Und in 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);
}
}
Dies ist eine Kombination von @ paul-blair s-Code ‚s Antwort auf Java umgewandelt, die einige Bereinigungen, die in @pushy gewesen zu sein scheinen darauf hingewiesen, von Paul Blair und einige Fehler enthält‘, die aus @Edward Campbell gemacht wird und anonym.
Ich kann gar nicht genug betonen, wie sehr dieser Code nur in Tests verwendet werden soll, und ist extrem Hacky. Aber für Fälle, in denen Sie die Umgebung Setup in Tests müssen es ist genau das, was ich brauchte.
Dazu gehören auch einige kleinere Berührungen von mir, dass der Code erlauben sowohl unter Windows läuft auf
zu arbeitenjava 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)
sowie Centos läuft auf
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)
Die Umsetzung:
/**
* 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));
}
Stochern online, es sieht aus wie es möglich sein könnte, dies mit JNI zu tun. Sie würden dann einen Anruf tätigen müssen, um putenv () von C, und Sie würden (vermutlich) haben es in einer Art und Weise zu tun, die auf Windows und UNIX gearbeitet.
Wenn alles, was getan werden kann, wäre es sicherlich nicht zu hart sein für Java selbst, dies zu unterstützen, anstatt mich in einer Zwangsjacke zu stellen.
Ein Perl-sprechender Freund schlägt vor, an anderer Stelle, dass dies, weil Umgebungsvariablen Prozess global ist und Java ist für eine gute Isolierung für gutes Design streben.
Versuchte aufdringlich Antwort oben und es zum größten Teil gearbeitet. Jedoch unter bestimmten Umständen, ich würde diese Ausnahme sehen:
java.lang.String cannot be cast to java.lang.ProcessEnvironment$Variable
Dies stellt sich heraus zu geschehen, wenn das Verfahren mehr als einmal aufgerufen wurde, um die Durchführung bestimmter innerer Klassen von ProcessEnvironment.
aufgrund Wenn die setEnv(..)
Methode mehr als einmal aufgerufen wird, wenn die Schlüssel aus der theEnvironment
Karte abgerufen werden, sind sie jetzt Strings (nachdem durch den ersten Aufruf von setEnv(...)
setzte in als Strings worden ist) und nicht auf die Karte der generischen Typen umgewandelt werden kann, Variable,
, die eine private innere Klasse von ProcessEnvironment.
ist
Eine feste Version (in Scala), ist unten. Hoffentlich ist es nicht zu schwierig ist, in Java zu übertragen.
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()
}
}
Wie die meisten Menschen, die diesen Thread gefunden haben, war ich einige Unit-Tests und benötigt das Schreiben der Umgebungsvariablen zu ändern, um die richtigen Bedingungen für den Test einstellen zu laufen. Allerdings fand ich die meisten upvoted Antworten hatten einige Probleme und / oder waren sehr kryptisch oder zu kompliziert. Hoffentlich wird dies anderen helfen, die Lösung schneller zu sortieren.
Zunächst einmal, fand ich endlich @Hubert Grzeskowiak Lösung die einfachste zu sein, und es funktionierte für mich. Ich wünschte, ich würde zuerst zu, dass man gekommen ist. Es basiert auf @Edward Campbell Antwort, aber ohne erschwerende für Schleife Suche.
Allerdings begann ich mit @ aufdringlich-Lösung, die die meisten upvotes bekam. Es ist eine Kombination aus @anonymous und @Edward Campbell. @pushy Ansprüche sind beide Ansätze benötigt sowohl Linux als auch Windows-Umgebungen abzudecken. Ich bin unter OS X laufen und feststellen, dass beide arbeiten (einmal ein Problem mit @anonymous Ansatz festgelegt ist). Wie andere bereits erwähnt haben, funktioniert diese Lösung die meiste Zeit, aber nicht alle.
Ich denke, die Quelle der meisten der Verwirrung von @ anonymouss Lösung kommt auf die ‚theEnvironment‘ Feld arbeitet. Mit Blick auf die Definition der ProcessEnvironment Struktur, 'theEnvironment' ist keine Map
In meinem Fall musste ich einige der Umgebungsvariablen für meinen Test entfernen (die anderen sollten unverändert). Dann wollte ich die Umgebungsvariablen in ihren vorherigen Zustand nach dem Test wieder herzustellen. Die Routinen unten machen, dass gerade nach vorne zu tun. Getestet habe ich beide Versionen von getModifiableEnvironmentMap auf OS X, und beide arbeiten äquivalent. Obwohl auf Kommentare in diesem Thread basiert, kann man eine bessere Wahl sein als die anderen abhängig von der Umgebung.
. Hinweis: Ich habe keinen Zugriff auf die ‚theCaseInsensitiveEnvironmentField‘ einzubeziehen, sofern diese Windows-spezifisch zu sein scheint, und ich hatte keine Möglichkeit, es zu testen, aber das Hinzufügen es sollte einfach sein
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);
}
Dies ist die Kotlin böse Version des bösen beantworten =)
@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)
}
}
}
Es ist in macOS Mojave zumindest arbeiten.
Kotlin Umsetzung Ich habe kürzlich basierend auf Edwards Antwort:
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)
}
}
Sie können Ihren ersten Java-Prozess übergeben Parameter in mit -D:
java -cp <classpath> -Dkey1=value -Dkey2=value ...