ฉันจะตั้งค่าตัวแปรสภาพแวดล้อมจาก Java ได้อย่างไร


289

ฉันจะตั้งค่าตัวแปรสภาพแวดล้อมจาก Java ได้อย่างไร ProcessBuilderผมเห็นว่าผมสามารถทำเช่นนี้สำหรับกระบวนการย่อยใช้ ฉันมีหลายกระบวนการย่อยที่จะเริ่มต้นดังนั้นฉันจึงค่อนข้างจะปรับเปลี่ยนสภาพแวดล้อมของกระบวนการปัจจุบันและให้กระบวนการย่อยได้รับมรดก

มีSystem.getenv(String)สำหรับการรับตัวแปรสภาพแวดล้อมเดียว ฉันยังสามารถได้รับของชุดที่สมบูรณ์ของตัวแปรสภาพแวดล้อมด้วยMap System.getenv()แต่การเรียกร้องput()สิ่งเหล่านี้Mapทำให้UnsupportedOperationExceptionดูเหมือนเป็นการอ่านอย่างเดียว System.setenv()และไม่มี

ดังนั้นมีวิธีการตั้งค่าตัวแปรสภาพแวดล้อมในกระบวนการทำงานอยู่หรือไม่ ถ้าเป็นเช่นนั้นได้อย่างไร ถ้าไม่เหตุผลคืออะไร (เป็นเพราะนี่คือ Java และดังนั้นฉันไม่ควรทำสิ่งชั่วร้ายที่ไม่สามารถอธิบายสิ่งที่ล้าสมัยเช่นการสัมผัสกับสภาพแวดล้อมของฉันได้หรือไม่) และถ้าไม่คำแนะนำที่ดีสำหรับการจัดการการเปลี่ยนแปลงตัวแปรสภาพแวดล้อมที่ฉันจะต้องให้อาหาร กระบวนการย่อย?


System.getEnv () มีวัตถุประสงค์เพื่อเป็น universal-ish บางสภาพแวดล้อมไม่ได้มีตัวแปรสภาพแวดล้อม
b1nary.atr0phy

7
สำหรับทุกคนที่ต้องการสิ่งนี้สำหรับกรณีทดสอบการใช้งานหน่วย: stackoverflow.com/questions/8168884/…
Atifm

สำหรับ Scala ให้ใช้สิ่งนี้: gist.github.com/vpatryshev/b1bbd15e2b759c157b58b68c58891ff4
Vlad Patryshev

คำตอบ:


88

(เป็นเพราะนี่คือ Java และดังนั้นฉันไม่ควรทำสิ่งที่ล้าสมัยที่ไม่สามารถอธิบายสิ่งที่ล้าสมัยเช่นการสัมผัสกับสภาพแวดล้อมของฉันได้)

ฉันคิดว่าคุณโดนตะปูที่หัว

วิธีที่เป็นไปได้ในการแบ่งเบาภาระคือการแยกวิธีการออก

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

และส่งผ่านProcessBuilders ก่อนที่จะเริ่ม

นอกจากนี้คุณอาจจะรู้อยู่แล้วว่านี้ ProcessBuilderแต่คุณสามารถเริ่มต้นกระบวนการมากกว่าหนึ่งด้วยเหมือนกัน ดังนั้นหากกระบวนการย่อยของคุณเหมือนกันคุณไม่จำเป็นต้องทำการตั้งค่านี้ซ้ำแล้วซ้ำอีก


1
เป็นการจัดการความอัปยศจะไม่ให้ฉันใช้ภาษาแบบพกพาที่แตกต่างกันสำหรับการเรียกใช้ชุดความชั่วร้ายชุดย่อยที่ล้าสมัยแล้ว :)
skiphoppy

18
S.Lott ฉันไม่ต้องการตั้งสภาพแวดล้อมของผู้ปกครอง ฉันกำลังมองหาการตั้งค่าสภาพแวดล้อมของตัวเอง
skiphoppy

3
มันใช้งานได้ดีเว้นแต่จะเป็นห้องสมุดของคนอื่น (เช่นซัน) ที่เปิดตัวกระบวนการ
sullivan-

24
@ b1naryatr0phy คุณพลาดประเด็นไป ไม่มีใครสามารถเล่นกับตัวแปรสภาพแวดล้อมของคุณได้เนื่องจากตัวแปรเหล่านั้นเป็นโลคัลสำหรับกระบวนการ (สิ่งที่คุณตั้งไว้ใน Windows คือค่าเริ่มต้น) แต่ละขั้นตอนมีอิสระที่จะเปลี่ยนตัวแปรของตัวเอง ... ยกเว้น Java
maaartinus

9
ข้อ จำกัด ของจาวานี้เป็นตำรวจนิดหน่อย ไม่มีเหตุผลใดที่จาวาไม่ยอมให้คุณตั้ง env vars นอกเหนือจาก "เพราะเราไม่ต้องการให้จาวาทำสิ่งนี้"
IanNorton

232

สำหรับใช้ในสถานการณ์ที่คุณต้องตั้งค่าสภาพแวดล้อมเฉพาะสำหรับการทดสอบหน่วยคุณอาจพบว่าแฮ็คต่อไปนี้มีประโยชน์ มันจะเปลี่ยนตัวแปรสภาพแวดล้อมทั่ว JVM (เพื่อให้แน่ใจว่าคุณรีเซ็ตการเปลี่ยนแปลงใด ๆ หลังจากการทดสอบของคุณ) แต่จะไม่เปลี่ยนสภาพแวดล้อมระบบของคุณ

ฉันพบว่าการรวมกันของสองแฮ็กสกปรกโดย Edward Campbell และนิรนามทำงานได้ดีที่สุดเนื่องจากหนึ่งในนั้นไม่ทำงานภายใต้ Linux หนึ่งไม่ทำงานภายใต้ windows 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);
      }
    }
  }
}

ใช้งานได้เหมือนจับใจ เครดิตเต็มให้กับผู้เขียนทั้งสองของแฮ็กเหล่านี้


1
สิ่งนี้จะเปลี่ยนในหน่วยความจำเท่านั้นหรือจะเปลี่ยนตัวแปรสภาพแวดล้อมทั้งหมดในระบบจริงหรือ
Shervin Asgari

36
สิ่งนี้จะเปลี่ยนตัวแปรสภาพแวดล้อมในหน่วยความจำเท่านั้น สิ่งนี้เป็นสิ่งที่ดีสำหรับการทดสอบเนื่องจากคุณสามารถตั้งค่าตัวแปรสภาพแวดล้อมได้ตามความจำเป็นสำหรับการทดสอบของคุณ แต่ให้ปล่อย envs ไว้ในระบบเหมือนเดิม ที่จริงแล้วฉันขอกีดกันไม่ให้ใครใช้รหัสนี้เพื่อจุดประสงค์อื่นนอกเหนือจากการทดสอบ รหัสนี้เป็นความชั่วร้าย ;-)
เร่ง

9
ในฐานะที่เป็น FYI JVM จะสร้างสำเนาของตัวแปรสภาพแวดล้อมเมื่อเริ่มต้น สิ่งนี้จะแก้ไขสำเนานั้นไม่ใช่ตัวแปรสภาวะแวดล้อมสำหรับกระบวนการพาเรนต์ที่เริ่มต้น JVM
bmeding

ฉันลองสิ่งนี้บน Android แล้วดูเหมือนจะไม่เป็นเช่นนั้น ใครมีโชคบน Android บ้าง
Hans-Christoph Steiner

5
แน่นอนimport java.lang.reflect.Field;
รุก

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

หรือเพื่อเพิ่ม / อัปเดต var เดี่ยวและลบลูปตามคำแนะนำของ 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);
  }

3
ดูเหมือนว่าจะแก้ไขแผนที่ในหน่วยความจำ แต่จะบันทึกค่าลงในระบบหรือไม่
Jon Onstott

1
มันจะเปลี่ยนแผนที่หน่วยความจำของตัวแปรสภาพแวดล้อม ฉันเดาว่าเพียงพอในการใช้งานหลายกรณี @ เอ็ดเวิร์ด - เอ้ยมันยากที่จะจินตนาการว่าวิธีการแก้ปัญหานี้ได้รับการพิจารณาในสถานที่แรก!
anirvan

13
สิ่งนี้จะไม่เปลี่ยนตัวแปรสภาพแวดล้อมในระบบ แต่จะเปลี่ยนแปลงตัวแปรเหล่านี้ในการเรียกใช้ปัจจุบันของ Java สิ่งนี้มีประโยชน์มากสำหรับการทดสอบหน่วย
Stuart K

10
ทำไมไม่ใช้Class<?> cl = env.getClass();แทนที่จะเป็นห่วง
thejoshwolfe

1
นี่คือสิ่งที่ฉันกำลังมองหา! ฉันกำลังเขียนการทดสอบการรวมสำหรับโค้ดบางตัวที่ใช้เครื่องมือของบุคคลที่สามด้วยเหตุผลบางอย่างช่วยให้คุณปรับเปลี่ยนความยาวการหมดเวลาเริ่มต้นสั้น ๆ อย่างไร้เหตุผลด้วยตัวแปรสภาพแวดล้อม
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);
}

17

บน Android อินเทอร์เฟซถูกเปิดเผยผ่าน Libcore.os เป็นชนิดของ API ที่ซ่อนอยู่

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

คลาส Libcore รวมถึงส่วนต่อประสานระบบปฏิบัติการนั้นเป็นแบบสาธารณะ เพียงแค่การประกาศในชั้นเรียนขาดหายไปและจำเป็นต้องแสดงให้ลิงเกอร์ทราบ ไม่จำเป็นต้องเพิ่มคลาสลงในแอปพลิเคชัน แต่ก็ไม่เจ็บถ้ารวมอยู่ด้วย

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

1
ผ่านการทดสอบและทำงานบน Android 4.4.4 (CM11) PS ปรับเดียวที่ฉันทำถูกแทนที่ด้วยthrows ErrnoException throws Exception
DavisNT

7
API 21 Os.setEnvตอนนี้ developer.android.com/reference/android/system/… , java.lang.String, บูลีน)
Jared Burrows

1
อาจหมดอายุด้วยข้อ จำกัด ใหม่ของ Pie: developer.android.com/about/versions/pie/…
TWiStErRob

13

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")หลังจากนี้คุณจะได้รับ"/bin/bash"กลับ


ข้างต้นไม่ทำงานใน windows 10 แต่จะทำงานใน linux
mengchengfeng

น่าสนใจ ฉันไม่ได้ลองด้วยตัวเองบน Windows คุณได้รับข้อผิดพลาด @mengchengfeng หรือไม่
Hubert Grzeskowiak

@HubertGrzeskowiak เราไม่เห็นข้อความแสดงข้อผิดพลาดมันไม่ทำงาน ...
mengchengfeng

9

นี่คือการรวมกันของคำตอบของ @ paul-blair ที่ถูกแปลงเป็น Java ซึ่งรวมถึงการสะสางบางส่วนที่ชี้ให้เห็นโดย paul blair และความผิดพลาดบางอย่างที่ดูเหมือนจะอยู่ในรหัสของ @pushy ซึ่งประกอบด้วย @Edward Campbell และไม่ระบุชื่อ

ฉันไม่สามารถเน้นได้ว่าควรใช้รหัสนี้เท่าไหร่ในการทดสอบและแฮ็คอย่างมาก แต่สำหรับกรณีที่คุณต้องการตั้งค่าสภาพแวดล้อมในการทดสอบมันเป็นสิ่งที่ฉันต้องการ

นอกจากนี้ยังรวมถึงการสัมผัสเล็กน้อยของฉันที่อนุญาตให้โค้ดทำงานบนทั้ง 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));
}

7

ปรากฎว่าโซลูชันจาก @ pushy / @ anonymous / @ Edward Campbell ไม่ทำงานบน Android เพราะ Android ไม่ใช่ Java โดยเฉพาะ Android ไม่มีjava.lang.ProcessEnvironmentเลย แต่มันจะง่ายกว่าใน Android คุณแค่ต้องทำการโทร JNI ไปที่ POSIXsetenv() :

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

และใน 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);
    }
}

5

เช่นเดียวกับคนส่วนใหญ่ที่พบเธรดนี้ฉันกำลังเขียนการทดสอบหน่วยและจำเป็นต้องแก้ไขตัวแปรสภาพแวดล้อมเพื่อตั้งค่าเงื่อนไขที่ถูกต้องสำหรับการทดสอบเพื่อให้ทำงานได้ อย่างไรก็ตามฉันพบคำตอบที่ upvoted ที่สุดมีปัญหาบางอย่างและ / หรือมีความลับหรือซับซ้อนเกินไป หวังว่านี่จะช่วยให้ผู้อื่นแก้ไขปัญหาได้เร็วขึ้น

ก่อนอื่นฉันก็พบวิธีแก้ปัญหาของ @Hubert Grzeskowiak ที่ง่ายที่สุดและใช้ได้สำหรับฉัน ฉันหวังว่าฉันจะได้มาที่หนึ่งก่อน มันขึ้นอยู่กับคำตอบของ @Edward Campbell แต่ไม่มีความซับซ้อนในการค้นหาลูป

อย่างไรก็ตามฉันเริ่มต้นด้วยวิธีแก้ปัญหาของ @ pushy ซึ่งมี upvotes มากที่สุด มันเป็นคำสั่งผสมของ @anonymous และ @Edward Campbell's @pushy อ้างว่าจำเป็นต้องใช้ทั้งสองวิธีเพื่อครอบคลุมทั้งสภาพแวดล้อม Linux และ Windows ฉันทำงานภายใต้ OS X และพบว่าทำงานได้ทั้งคู่ (เมื่อปัญหาเกี่ยวกับ @anonymous ได้รับการแก้ไขแล้ว) ดังที่คนอื่น ๆ ได้กล่าวถึงวิธีการแก้ปัญหานี้ใช้เวลาส่วนใหญ่ แต่ไม่ใช่ทั้งหมด

ฉันคิดว่าแหล่งที่มาของความสับสนส่วนใหญ่มาจากวิธีแก้ปัญหาของ @ anonymous ในส่วน 'theEnvironment' ดูที่คำจำกัดความของProcessEnvironmentโครงสร้าง 'theEnvironment' ไม่ใช่ Map <String, String> แต่เป็น Map <Variable, Value> การล้างแผนที่ใช้งานได้ดี แต่การดำเนินการ putAll จะสร้างแผนที่ใหม่เป็น Map <String, String> ซึ่งอาจทำให้เกิดปัญหาเมื่อการดำเนินการตามมาดำเนินการกับโครงสร้างข้อมูลโดยใช้ API ปกติที่คาดว่าแผนที่ <Variable, Value> นอกจากนี้การเข้าถึง / ลบองค์ประกอบแต่ละรายการก็เป็นปัญหาเช่นกัน ทางออกคือการเข้าถึง 'theEnvironment' ทางอ้อมผ่าน 'theUnmodifiableEnvironment' แต่เนื่องจากนี่เป็นประเภทUnmodifiableMapต้องทำการเข้าถึงผ่านตัวแปรส่วนตัว 'm' ของ UnmodifiableMap ดูที่ getModifiableEnvironmentMap2 ในโค้ดด้านล่าง

ในกรณีของฉันฉันต้องการที่จะลบตัวแปรสภาพแวดล้อมบางอย่างสำหรับการทดสอบของฉัน (คนอื่น ๆ ควรจะไม่เปลี่ยนแปลง) จากนั้นฉันต้องการเรียกคืนตัวแปรสภาพแวดล้อมสู่สถานะก่อนหน้าหลังการทดสอบ กิจวัตรด้านล่างทำให้ตรงไปตรงมาที่จะทำ ฉันทดสอบ getModifiableEnvironmentMap ทั้งสองเวอร์ชันบน OS X และทำงานได้เท่าเทียมกัน แม้ว่าตามความคิดเห็นในหัวข้อนี้หนึ่งอาจเป็นทางเลือกที่ดีกว่าอื่น ๆ ขึ้นอยู่กับสภาพแวดล้อม

หมายเหตุ: ฉันไม่ได้รวมการเข้าถึง '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);
}

ขอบคุณมันเป็นกรณีการใช้งานของฉันและภายใต้ mac os x ด้วย
Rafael Gonçalves

ชอบสิ่งนี้มากฉันทำให้รุ่น Groovy ง่ายขึ้นเล็กน้อยดูด้านล่าง
mike rodent

4

ดูเหมือนจะเป็นไปได้ที่จะทำสิ่งนี้กับ JNI จากนั้นคุณต้องโทรไปที่ putenv () จาก C และคุณ (น่าจะ) ต้องทำในลักษณะที่ทำงานได้ทั้งบน Windows และ UNIX

หากสิ่งที่สามารถทำได้ก็คงไม่ยากเกินไปสำหรับ Java ที่จะสนับสนุนสิ่งนี้แทนการใส่เสื้อแจ็คเก็ตตัวตรง

เพื่อนที่พูดภาษา Perl อื่นแนะนำว่านี่เป็นเพราะตัวแปรสภาพแวดล้อมเป็นกระบวนการทั่วโลกและ Java มุ่งมั่นที่จะแยกออกจากกันเพื่อการออกแบบที่ดี


ใช่คุณสามารถตั้งค่าสภาพแวดล้อมการประมวลผลจากรหัส C แต่ฉันจะไม่นับว่าการทำงานใน Java มีโอกาสที่ดีที่ JVM จะคัดลอกสภาพแวดล้อมไปยังวัตถุ Java String ในระหว่างการเริ่มต้นดังนั้นการเปลี่ยนแปลงของคุณจะไม่ถูกใช้สำหรับการดำเนินการ JVM ในอนาคต
Darron

ขอบคุณสำหรับคำเตือนดาร์รอน อาจเป็นโอกาสที่ดีที่คุณพูดถูก
skiphoppy

2
@Darron มีหลายเหตุผลที่เราต้องการทำสิ่งนี้ไม่มีอะไรเกี่ยวข้องกับสิ่งที่ JVM คิดว่าเป็นสภาพแวดล้อม (คิดถึงการตั้งค่าLD_LIBRARY_PATHก่อนการโทรRuntime.loadLibrary()การdlopen()โทรที่มันเรียกใช้จะดูที่สภาพแวดล้อมจริงไม่ใช่ความคิดของ Java ที่เหมือนกัน)
Charles Duffy

สิ่งนี้ใช้ได้กับกระบวนการย่อยที่เริ่มต้นโดยไลบรารีดั้งเดิม (ซึ่งในกรณีของฉันคือส่วนใหญ่) แต่น่าเสียดายที่ไม่สามารถใช้งานได้สำหรับกระบวนการย่อยที่เริ่มต้นด้วยคลาส Process หรือ ProcessBuilder ของ Java
ด่าน

4

พยายามตอบคำถามของ pushy ด้านบนและทำงานได้เป็นส่วนใหญ่ อย่างไรก็ตามในบางสถานการณ์ฉันจะเห็นข้อยกเว้นนี้:

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

สิ่งนี้กลายเป็นว่าเกิดขึ้นเมื่อวิธีการถูกเรียกมากกว่าหนึ่งครั้งเนื่องจากการใช้งานของชั้นในบางอย่างของProcessEnvironment.ถ้าsetEnv(..)วิธีการที่เรียกว่ามากกว่าหนึ่งครั้งเมื่อคีย์ถูกดึงมาจากtheEnvironmentแผนที่ตอนนี้พวกเขาจะถูกสตริง เป็นสตริงตามการร้องขอครั้งแรกของsetEnv(...)) และไม่สามารถส่งไปยังประเภททั่วไปของแผนที่ได้Variable,ซึ่งเป็นคลาสส่วนตัวภายในของProcessEnvironment.

รุ่นคงที่ (ใน Scala) อยู่ด้านล่าง หวังว่ามันจะไม่ยากเกินไปที่จะส่งต่อไปยัง 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()
  }
}

JavaClass กำหนดไว้ที่ไหน?
Mike Slinn

1
import java.lang.{Class => JavaClass}สันนิษฐานว่า
Randall Whitman

1
การใช้งานของ java.lang.ProcessEnvironment จะแตกต่างกันในแพลตฟอร์มที่แตกต่างกันแม้สำหรับการสร้างเดียวกัน ตัวอย่างเช่นไม่มีคลาส java.lang.ProcessEnvironment $ Variable ในการใช้งาน Windows แต่คลาสนี้มีอยู่ในหนึ่งสำหรับ Linux คุณสามารถตรวจสอบได้อย่างง่ายดาย เพียงดาวน์โหลด tar.gz JDK distribution สำหรับ Linux และแตกไฟล์จาก src.zip จากนั้นเปรียบเทียบกับไฟล์เดียวกันจากการกระจายสำหรับ Windows พวกเขาต่างกันโดยสิ้นเชิงใน JDK 1.8.0_181 ฉันไม่ได้ตรวจสอบพวกเขาใน Java 10 แต่ฉันจะไม่แปลกใจถ้ามีภาพเดียวกัน
Alex Konshin

1

นี่เป็นรุ่นชั่วของ Kotlin ของคำตอบที่ชั่วร้ายของ @ 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)
            }
        }
    }

มันทำงานใน macOS Mojave เป็นอย่างน้อย


0

หากคุณทำงานกับ SpringBoot คุณสามารถเพิ่มการระบุตัวแปรสภาพแวดล้อมในคุณสมบัติต่อไปนี้:

was.app.config.properties.toSystemProperties

1
คุณช่วยอธิบายหน่อยได้ไหม?
Faraz

0

ตัวแปรขึ้นอยู่กับคำตอบ@ pushy ของทำงานบน 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)

การใช้งาน:

old_environ = dict(os.environ)
old_environ['EPM_ORACLE_HOME'] = r"E:\Oracle\Middleware\EPMSystem11R1"
set_env(old_environ)

0

คำตอบของ Tim Ryan ทำงานสำหรับฉัน ... แต่ฉันต้องการ Groovy (ตัวอย่างจากบริบท Spock) และ 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|"

0

รุ่นใน Kotlin ในอัลกอริทึมนี้ฉันสร้างมัณฑนากรที่ช่วยให้คุณสามารถตั้งค่าและรับตัวแปรจากสภาพแวดล้อม

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

-1

การใช้ 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)
    }
}

-12

คุณสามารถส่งพารามิเตอร์ไปยังกระบวนการ java เริ่มต้นของคุณด้วย -D:

java -cp <classpath> -Dkey1=value -Dkey2=value ...

ค่าไม่เป็นที่รู้จักในเวลาดำเนินการ พวกเขากลายเป็นที่รู้จักในระหว่างการดำเนินการของโปรแกรมเมื่อผู้ใช้ให้ / เลือกพวกเขา และนั่นตั้งค่าคุณสมบัติระบบเท่านั้นไม่ใช่ตัวแปรสภาพแวดล้อม
skiphoppy

จากนั้นในกรณีนั้นคุณอาจต้องการหาวิธีปกติ (ผ่านพารามิเตอร์ args [] ไปยังวิธีการหลัก) เพื่อเรียกใช้กระบวนการย่อยของคุณ
matt b

ด้าน b, วิธีปกติคือผ่าน ProcessBuilder ดังกล่าวในคำถามเดิมของฉัน :)
skiphoppy

7
พารามิเตอร์ -D สามารถผ่านและไม่ได้เช่นเดียวกับSystem.getProperty System.getenvนอกจากนี้Systemคลาสยังอนุญาตให้ตั้งค่าคุณสมบัติเหล่านี้แบบคงที่โดยใช้setProperty
anirvan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.