java - 확인 - 자바 환경변수 설정 이유




Java로 환경 변수를 설정하려면 어떻게해야합니까? (9)

Java로 환경 변수를 설정하려면 어떻게해야합니까? ProcessBuilder 사용하여 하위 프로세스에 대해이 작업을 수행 할 수 있음을 알았습니다. 하지만 몇 가지 하위 프로세스를 시작해야합니다. 그래서 현재 프로세스의 환경을 수정하고 하위 프로세스가이를 상속하도록합니다.

단일 환경 변수를 가져 오는 System.getenv (String)가 있습니다. 또한 System.getenv ()를 사용하여 전체 환경 변수 집합에 대한 Map을 얻을 수 있습니다. 그러나 Map에서 put ()을 호출하면 UnsupportedOperationException이 throw됩니다. 이는 환경이 읽기 전용임을 의미합니다. 그리고 System.setenv ()도 없습니다.

그래서 현재 실행중인 프로세스에서 환경 변수를 설정하는 방법이 있습니까? 그렇다면 어떻게? 그렇지 않다면 그 이유는 무엇입니까? (자바인지, 따라서 내 환경을 만지기 같은 쓸모없는 이식 할 수없는 일을해서는 안되는가?) 그렇지 않다면 환경 변수를 관리하기위한 좋은 제안은 몇 가지로 먹이를 줄 필요가있다. 하위 프로세스?


(자바가 아니기 때문에 내 환경을 만지기 같은 악의적 인 이식 할 수없는 일을해서는 안되기 때문입니까?)

네가 머리에 못을 박은 것 같아.

부담을 덜어 줄 수있는 방법은 방법을 제외시키는 것입니다

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

ProcessBuilder 를 시작하기 전에 그것을 통해 모든 ProcessBuilder 전달하십시오.

또한 이미 알고 있을지 모르겠지만 동일한 ProcessBuilder 둘 이상의 프로세스를 시작할 수 있습니다. 따라서 하위 프로세스가 동일하면이 설정을 반복해서 수행 할 필요가 없습니다.


Linux 전용

단일 환경 변수 설정 (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);
    }
}

용법:

먼저 SystemUtil과 같이 원하는 클래스에 메서드를 넣습니다.

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

이 후에 System.getenv("SHELL") 를 호출 System.getenv("SHELL") "/bin/bash" 반환됩니다.


Kotlin 구현 최근 에드워드의 대답을 기반으로 만들었습니다.

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

단위 테스트를위한 특정 환경 값을 설정해야하는 시나리오에서 사용하려면 다음 해킹이 유용 할 수 있습니다. JVM 전체에서 환경 변수가 변경되므로 (테스트 후 변경 사항을 다시 설정해야 함) 시스템 환경이 변경되지는 않습니다.

나는 에드워드 캠벨 (Edward Campbell)과 익명의 작품에 의한 두 가지 더러운 해킹의 조합이 리눅스에서 작동하지 않는다는 것을 발견했다. 윈도우 7에서는 작동하지 않는다. 그래서 멀티 플랫폼 해킹을 얻으려면 나는 그들을 결합했다 :

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

이것은 매력처럼 작동합니다. 이 해킹의 두 저자에게 완전한 크레디트.


안드로이드에서 인터페이스는 Libcore.os를 통해 숨겨진 API의 일종으로 노출됩니다.

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

Libcore 클래스는 물론 인터페이스 OS도 공개되어 있습니다. 클래스 선언 만 빠져있어서 링커에게 보여줘야합니다. 응용 프로그램에 클래스를 추가 할 필요는 없지만 클래스가 포함 되어도 상처를주지는 않습니다.

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

온라인을 둘러 보니 JNI로이 작업을 수행 할 수있는 것처럼 보입니다. 그런 다음 C에서 putenv ()를 호출해야하며 Windows와 UNIX 모두에서 작동하는 방식으로해야 할 것입니다 (아마도).

모든 일을 할 수 있다면, 자바 자체가 나를 똑바로 재킷에 넣는 대신에 이것을 지원하는 것은 그리 어렵지 않을 것입니다.

Perl을 사용하는 친구는 환경 변수가 프로세스 전역이며 Java가 좋은 디자인을 위해 좋은 분리를 위해 노력하고 있기 때문에 이것이 다른 곳에서 제안합니다.


이 스레드를 찾은 대부분의 사람들과 마찬가지로, 나는 단위 테스트를 작성하고 테스트를 실행하기위한 올바른 조건을 설정하기 위해 환경 변수를 수정해야했습니다. 그러나, 나는 대부분의 upvoted 대답이 약간의 이슈를 가지고 있거나 매우 또는 매우 지나치게 복잡하거나 지나치게 복잡하다는 것을 발견했다. 다행히도 다른 사람들이 솔루션을 더 빨리 분류 할 수 있기를 바랍니다.

먼저, @Hubert Grzeskowiak의 솔루션이 가장 단순하다는 것을 마침내 발견했으며 나에게 도움이되었습니다. 내가 먼저 그걸로 왔으면 좋았을 텐데. 그것은 @Edward Campbell의 대답을 기반으로하지만 루프 검색을 복잡하게하지는 않습니다.

그러나, 나는 대부분의 upvotes있어 @ pushy의 솔루션으로 시작했다. @ 익명 및 @ 에드워드 캠벨의 콤보입니다. Linux와 Windows 환경 모두를 다루기 위해서는 두 가지 접근 방식이 모두 필요하다고 주장합니다. 저는 OS X에서 실행 중이며 두 작업 (한 번 익명 접근법을 가진 문제가 수정 됨)을 발견했습니다. 다른 사람들이 지적했듯이이 솔루션은 대부분의 경우 작동하지만 전부는 아닙니다.

나는 대부분의 혼란의 근원이 'theEnvironment'분야에서 작동하는 @ 익명의 해결책으로부터 비롯된 것이라고 생각한다. ProcessEnvironment 구조체의 정의를 살펴보면 'theEnvironment'는 Map <String, String>이 ​​아니라 Map <Variable, Value>입니다. 맵을 지우는 것은 정상적으로 작동하지만 putAll 작업은 map <String, String> 맵을 다시 작성합니다. Map <String, String>을 필요로하는 일반 API를 사용하여 후속 작업이 데이터 구조에서 작동 할 때 문제가 발생할 수 있습니다. 또한 개별 요소에 대한 액세스 / 제거가 문제입니다. 해결 방법은 'theUnvironment 환경'을 통해 간접적으로 'theEnvironment'에 액세스하는 것입니다. 그러나 이것은 UnmodifiableMap 유형이므로 UnmodifiableMap 유형의 개인 변수 'm'을 통해 액세스해야합니다. 아래 코드에서 getModifiableEnvironmentMap2를 참조하십시오.

필자의 경우 테스트를 위해 일부 환경 변수를 제거해야했습니다 (나머지는 변경되지 않아야 함). 그런 다음 환경 변수를 테스트 후 이전 상태로 복원하려고했습니다. 아래의 루틴은 바로 할 수 있도록합니다. 필자는 OS X에서 getModifiableEnvironmentMap의 두 버전을 모두 테스트했으며 동등하게 작동합니다. 이 스레드의 주석을 기반으로하지만 환경에 따라 다른 것보다 더 나은 선택 일 수 있습니다.

참고 : 'theCaseInsensitiveEnvironmentField'에 대한 액세스는 Windows 특정 것으로 보이고 테스트 할 수있는 방법이 없기 때문에 액세스를 포함하지 않았지만 추가하는 것은 간단합니다.

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

이것은 @ paul-blair의 대답을 Java로 변환 한 것으로 폴 블레어가 지적한 정리와 @pdoty의 코드 안에있는 @Edward Campbell과 익명의 실수를 포함합니다.

나는이 코드가 테스트에서 얼마나 많이 사용되어야하고 매우 해킹되는지를 강조 할 수 없다. 그러나 테스트에서 환경 설정이 필요한 경우 정확하게 필요한 부분입니다.

여기에는 또한 코드가 실행되는 Windows와 Windows에서 실행되도록 허용하는 몇 가지 사소한 문제가 포함되어 있습니다.

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)

뿐만 아니라 Centos는

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)

구현 :

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

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




environment-variables