Làm cách nào để đặt biến môi trường từ Java?


289

Làm cách nào để đặt biến môi trường từ Java? Tôi thấy rằng tôi có thể làm điều này cho các quy trình con bằng cách sử dụng ProcessBuilder. Tuy nhiên, tôi có một số quy trình con để bắt đầu, vì vậy tôi muốn sửa đổi môi trường của quy trình hiện tại và để các quy trình con kế thừa nó.

Có một System.getenv(String)để có được một biến môi trường duy nhất. Tôi cũng có thể nhận được một Maptập hợp đầy đủ các biến môi trường với System.getenv(). Nhưng, kêu gọi put()điều đó Mapném một UnsupportedOperationException- rõ ràng chúng chỉ có nghĩa là môi trường chỉ được đọc. Và, không có System.setenv().

Vì vậy, có cách nào để thiết lập các biến môi trường trong quy trình hiện đang chạy không? Nếu vậy thì thế nào? Nếu không, lý do là gì? (Có phải vì đây là Java và do đó tôi không nên làm những điều lỗi thời không thể kiểm soát được như chạm vào môi trường của mình?) quy trình con?


System.getEnv () được dự định là phổ quát-ish, một số môi trường thậm chí không có biến môi trường.
b1nary.atr0phy

7
Đối với bất kỳ ai cần điều này cho trường hợp sử dụng thử nghiệm đơn vị: stackoverflow.com/questions/8168884/iêu
Atifm

Đối với Scala, hãy sử dụng cái này: gist.github.com/vpatryshev/b1bbd15e2b759c157b58b68c58891ff4
Vlad Patryshev

Câu trả lời:


88

(Có phải vì đây là Java và do đó tôi không nên làm những điều lỗi thời không thể kiểm soát được như chạm vào môi trường của mình?)

Tôi nghĩ rằng bạn đã đánh vào đầu đinh.

Một cách có thể để giảm bớt gánh nặng sẽ là đưa ra một phương pháp

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

và vượt qua bất kỳ ProcessBuilders thông qua nó trước khi bắt đầu chúng.

Ngoài ra, bạn có thể đã biết điều này, nhưng bạn có thể bắt đầu nhiều hơn một quy trình với cùng ProcessBuilder. Vì vậy, nếu các quy trình con của bạn giống nhau, bạn không cần phải thực hiện thiết lập này nhiều lần.


1
Đó là một sự quản lý xấu hổ sẽ không cho phép tôi sử dụng một ngôn ngữ di động khác để chạy bộ quy trình con xấu xa, lỗi thời này. :)
Skiphoppy

18
S.Lott, tôi không muốn thiết lập môi trường của cha mẹ. Tôi đang tìm cách thiết lập môi trường của riêng tôi.
Skiphoppy

3
Điều đó hoạt động rất tốt, trừ khi đó là thư viện của người khác (ví dụ: Sun) đang khởi động quá trình.
sullivan-

24
@ b1naryatr0phy Bạn đã bỏ lỡ điểm. Không ai có thể chơi với các biến môi trường của bạn vì các biến đó là cục bộ của một quy trình (những gì bạn đặt trong Windows là các giá trị mặc định). Mỗi quá trình có thể tự do thay đổi các biến của chính nó ... trừ khi Java của nó.
maaartinus

9
Hạn chế này của java là một chút cảnh sát. Không có lý do gì để java không cho phép bạn đặt env vars ngoài "vì chúng tôi không muốn java làm điều này".
IanNorton

232

Để sử dụng trong các tình huống mà bạn cần đặt giá trị môi trường cụ thể cho các thử nghiệm đơn vị, bạn có thể thấy bản hack sau hữu ích. Nó sẽ thay đổi các biến môi trường trong suốt JVM (vì vậy hãy đảm bảo bạn đặt lại bất kỳ thay đổi nào sau khi thử nghiệm), nhưng sẽ không thay đổi môi trường hệ thống của bạn.

Tôi thấy rằng sự kết hợp giữa hai bản hack bẩn của Edward Campbell và nặc danh hoạt động tốt nhất, vì một trong số đó không hoạt động trong linux, một bản không hoạt động dưới windows 7. Vì vậy, để có được một bản hack ác đa nền tảng, tôi đã kết hợp chúng:

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

Công việc này như một cái duyên vậy. Tín dụng đầy đủ cho hai tác giả của những hack này.


1
Điều này sẽ chỉ thay đổi trong bộ nhớ, hoặc thực sự thay đổi toàn bộ biến môi trường trong hệ thống?
Shervin Asgari

36
Điều này sẽ chỉ thay đổi biến môi trường trong bộ nhớ. Điều này tốt cho việc kiểm tra, bởi vì bạn có thể đặt biến môi trường là cần thiết cho thử nghiệm của mình, nhưng để lại các env trong hệ thống như hiện tại. Trên thực tế, tôi sẽ không khuyến khích bất kỳ ai sử dụng mã này cho bất kỳ mục đích nào khác ngoài thử nghiệm. Mã này là xấu xa ;-)
Pushy

9
Là một FYI, JVM tạo một bản sao của các biến môi trường khi nó bắt đầu. Điều này sẽ chỉnh sửa bản sao đó, không phải các biến môi trường cho quy trình cha đã khởi động JVM.
khoanh vùng

Tôi đã thử điều này trên Android và dường như không có. Bất cứ ai khác có bất kỳ may mắn trên Android?
Hans-Christoph Steiner

5
Chắc chắn,import java.lang.reflect.Field;
tự đề cao

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

Hoặc để thêm / cập nhật một var duy nhất và xóa vòng lặp theo đề xuất của 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
Nghe có vẻ như sẽ sửa đổi bản đồ trong bộ nhớ, nhưng liệu nó có lưu giá trị cho hệ thống không?
Jon Onstott

1
nó cũng thay đổi bản đồ bộ nhớ của các biến môi trường. Tôi đoán rằng đủ trong rất nhiều trường hợp sử dụng. @Edward - trời ạ, thật khó để tưởng tượng giải pháp này đã được tìm ra như thế nào ngay từ đầu!
anirvan

13
Điều này sẽ không thay đổi các biến môi trường trên hệ thống, nhưng sẽ thay đổi chúng trong lệnh gọi hiện tại của Java. Điều này rất hữu ích cho thử nghiệm đơn vị.
Stuart K

10
Tại sao không sử dụng Class<?> cl = env.getClass();thay vì vòng lặp đó?
thejoshwolfe

1
Đây chính xác là những gì tôi đang tìm kiếm! Tôi đã viết các bài kiểm tra tích hợp cho một số mã sử dụng công cụ của bên thứ ba, vì một số lý do, chỉ cho phép bạn sửa đổi độ dài thời gian chờ mặc định ngắn vô lý của nó với một biến môi trường.
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

trên Android giao diện được hiển thị thông qua Libcore.os như một loại API ẩn.

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

Lớp Libcore cũng như hệ điều hành giao diện là công khai. Chỉ cần khai báo lớp và cần được hiển thị cho trình liên kết. Không cần thêm các lớp vào ứng dụng, nhưng nó cũng không gây hại nếu nó được bao gồm.

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
Đã thử nghiệm và hoạt động trên Android 4.4.4 (CM11). PS Điều chỉnh duy nhất tôi đã thực hiện là thay thế throws ErrnoExceptionbằng throws Exception.
DavisNT

7
API 21, Os.setEnvhiện đã có . developer.android.com/reference/android/system/ Kẻ, java.lang.String, boolean)
Jared Burrows

1
Hiện tại có khả năng không còn tồn tại với các hạn chế mới của Pie: developer.android.com/about/versions/pie/
triệt

13

Chỉ Linux

Đặt các biến môi trường đơn (dựa trên câu trả lời của 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);
    }
}

Sử dụng:

Đầu tiên, đặt phương thức vào bất kỳ lớp nào bạn muốn, ví dụ SystemUtil. Sau đó gọi nó là tĩnh:

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

Nếu bạn gọi System.getenv("SHELL")sau này, bạn sẽ nhận "/bin/bash"lại.


Ở trên không hoạt động trong windows 10, nhưng sẽ hoạt động trong linux.
mengchengfeng

Hấp dẫn. Tôi đã không thử nó trên Windows. Bạn có nhận được một lỗi, @mengchengfeng?
Hubert Grzeskowiak

@HubertGrzeskowiak Chúng tôi không thấy bất kỳ thông báo lỗi nào, nó chỉ không hoạt động ...
mengchengfeng

9

Đây là sự kết hợp giữa câu trả lời của @ paul-blair được chuyển đổi sang Java, bao gồm một số phần dọn dẹp được chỉ ra bởi paul blair và một số lỗi dường như nằm trong mã của @pushy được tạo thành từ @Edward Campbell và ẩn danh.

Tôi không thể nhấn mạnh bao nhiêu mã này CHỈ nên được sử dụng trong thử nghiệm và cực kỳ hack. Nhưng đối với những trường hợp bạn cần thiết lập môi trường trong các bài kiểm tra thì đó chính xác là những gì tôi cần.

Điều này cũng bao gồm một số chạm nhỏ của tôi cho phép mã hoạt động trên cả Windows chạy trên

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)

cũng như Centos đang chạy

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)

Việc thực hiệ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));
}

7

Hóa ra giải pháp từ @ Pushy / @ nặc danh / @ Edward Campbell không hoạt động trên Android vì Android không thực sự là Java. Cụ thể, Android hoàn toàn không có java.lang.ProcessEnvironment. Nhưng hóa ra Android dễ dàng hơn, bạn chỉ cần thực hiện cuộc gọi JNI tới POSIX setenv():

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

Và trong 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

Giống như hầu hết những người đã tìm thấy chủ đề này, tôi đã viết một số bài kiểm tra đơn vị và cần sửa đổi các biến môi trường để đặt các điều kiện chính xác cho bài kiểm tra chạy. Tuy nhiên, tôi thấy các câu trả lời được đánh giá cao nhất có một số vấn đề và / hoặc rất khó hiểu hoặc quá phức tạp. Hy vọng rằng điều này sẽ giúp những người khác sắp xếp giải pháp nhanh hơn.

Trước hết, cuối cùng tôi đã tìm thấy giải pháp của @Hubert Grzeskowiak là đơn giản nhất và nó hiệu quả với tôi. Tôi ước tôi sẽ đến cái đó trước. Nó dựa trên câu trả lời của @Edward Campbell, nhưng không phức tạp cho tìm kiếm vòng lặp.

Tuy nhiên, tôi đã bắt đầu với giải pháp @ Pushy, giải pháp được nhiều người ủng hộ nhất. Nó là sự kết hợp của @anonymous và @Edward Campbell's. @pushy tuyên bố cả hai cách tiếp cận đều cần thiết để bao quát cả môi trường Linux và Windows. Tôi đang chạy trên OS X và thấy rằng cả hai đều hoạt động (một khi vấn đề với cách tiếp cận @anonymous được khắc phục). Như những người khác đã lưu ý, giải pháp này hoạt động hầu hết thời gian, nhưng không phải tất cả.

Tôi nghĩ rằng nguồn gốc của hầu hết sự nhầm lẫn đến từ giải pháp của @ nặc danh hoạt động trên trường 'môi trường'. Nhìn vào định nghĩa của cấu trúc ProcessEn Môi trường, 'Môi trường' không phải là Bản đồ <Chuỗi, Chuỗi> mà là Bản đồ <Biến, Giá trị>. Xóa bản đồ hoạt động tốt, nhưng thao tác putAll sẽ xây dựng lại bản đồ <Chuỗi, Chuỗi>, có khả năng gây ra sự cố khi các hoạt động tiếp theo hoạt động trên cấu trúc dữ liệu bằng API thông thường mong đợi Bản đồ <Biến, Giá trị>. Ngoài ra, truy cập / loại bỏ các yếu tố cá nhân là một vấn đề. Giải pháp là truy cập gián tiếp vào 'môi trường' thông qua 'theUnmodifiableEn Môi trường'. Nhưng vì đây là loại UnmodifiableMapviệc truy cập phải được thực hiện thông qua biến riêng 'm' của loại UnmodifiableMap. Xem getModifiableEn MôiMap2 trong mã bên dưới.

Trong trường hợp của tôi, tôi cần phải loại bỏ một số biến môi trường cho thử nghiệm của mình (những biến khác không thay đổi). Sau đó, tôi muốn khôi phục các biến môi trường về trạng thái trước của chúng sau khi thử nghiệm. Các thói quen dưới đây làm cho thẳng về phía trước để làm. Tôi đã thử nghiệm cả hai phiên bản getModifiableEn Môi trường trên OS X và cả hai đều hoạt động tương đương. Mặc dù dựa trên các nhận xét trong chủ đề này, một người có thể là một lựa chọn tốt hơn so với người khác tùy thuộc vào môi trường.

Lưu ý: Tôi không bao gồm quyền truy cập vào 'theCaseInsensitiveEn MôiField' vì đó có vẻ là đặc thù của Windows và tôi không có cách nào để kiểm tra nó, nhưng việc thêm nó sẽ rất đơn giản.

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

Cảm ơn, đó chính xác là trường hợp sử dụng của tôi và theo mac os x.
Rafael Gonçalves

Thích điều này rất nhiều, tôi đã tạo ra một phiên bản đơn giản hơn cho Groovy, xem bên dưới.
mike gặm nhấm

4

Chọc qua mạng, có vẻ như có thể làm điều này với JNI. Sau đó, bạn phải thực hiện cuộc gọi tới putenv () từ C và bạn (có lẽ) phải thực hiện theo cách hoạt động trên cả Windows và UNIX.

Nếu tất cả những gì có thể được thực hiện, chắc chắn Java sẽ không quá khó khăn để hỗ trợ điều này thay vì đưa tôi vào một chiếc áo khoác thẳng.

Một người bạn nói tiếng Perl ở nơi khác cho rằng điều này là do các biến môi trường là quá trình toàn cầu và Java đang cố gắng tách biệt tốt để thiết kế tốt.


Có, bạn có thể đặt môi trường quy trình từ mã C. Nhưng tôi sẽ không tin vào việc làm việc trong Java. Có một cơ hội tốt khi JVM sao chép môi trường vào các đối tượng Chuỗi Java trong khi khởi động, vì vậy những thay đổi của bạn sẽ không được sử dụng cho các hoạt động JVM trong tương lai.
Darron

Cảm ơn vì lời cảnh báo, Darron. Có lẽ bạn có cơ hội tốt.
Skiphoppy

2
@Darron nhiều lý do người ta muốn làm điều này hoàn toàn không liên quan gì đến những gì JVM nghĩ về môi trường. (Nghĩ đến việc thiết lập LD_LIBRARY_PATHtrước khi gọi Runtime.loadLibrary(); dlopen()cuộc gọi mà nó gọi sẽ nhìn vào môi trường thực chứ không phải ý tưởng tương tự của Java).
Charles Duffy

Điều này hoạt động cho các quy trình con được bắt đầu bởi một thư viện riêng (trong trường hợp của tôi là hầu hết trong số chúng), nhưng không may là không hoạt động cho các quy trình con được bắt đầu bởi các lớp Process hoặc ProcessBuilder của Java.
Dan

4

Đã thử câu trả lời của Pushy ở trên và nó đã làm việc cho hầu hết các phần. Tuy nhiên, trong một số trường hợp nhất định, tôi sẽ thấy ngoại lệ này:

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

Điều này hóa ra xảy ra khi phương thức được gọi nhiều lần, do việc triển khai một số lớp bên trong của ProcessEnvironment.Nếu setEnv(..)phương thức được gọi nhiều hơn một lần, khi các khóa được lấy từ theEnvironmentbản đồ, giờ đây chúng là các chuỗi (đã được đưa vào dưới dạng chuỗi theo cách gọi đầu tiên của setEnv(...)) và không thể chuyển sang loại chung của bản đồ, Variable,là lớp bên trong riêng củaProcessEnvironment.

Một phiên bản cố định (trong Scala), bên dưới. Hy vọng rằng không quá khó để chuyển sang 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 được định nghĩa ở đâu?
Mike Slinn

1
Có lẽ import java.lang.{Class => JavaClass}.
Randall Whitman

1
Việc triển khai java.lang.ProcessEn Môi trường là khác nhau trên các nền tảng khác nhau ngay cả đối với cùng một bản dựng. Ví dụ, không có lớp java.lang.ProcessEn Môi trường $ Biến trong việc triển khai Windows nhưng lớp này tồn tại trong một lớp cho Linux. Bạn có thể dễ dàng kiểm tra nó. Chỉ cần tải xuống bản phân phối JDK tar.gz cho Linux và trích xuất nguồn từ src.zip sau đó so sánh nó với cùng một tệp từ bản phân phối cho Windows. Chúng hoàn toàn khác nhau trong JDK 1.8.0_181. Tôi đã không kiểm tra chúng trong Java 10 nhưng tôi sẽ không ngạc nhiên nếu có cùng một hình ảnh.
Alex Konshin

1

Đây là phiên bản ác của Kotlin trong câu trả lời độc ác của @ 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)
            }
        }
    }

Nó đang hoạt động trong macOS Mojave ít nhất.


0

Nếu bạn làm việc với SpringBoot, bạn có thể thêm chỉ định biến môi trường trong thuộc tính sau:

was.app.config.properties.toSystemProperties

1
Bạn có thể vui lòng giải thích một chút?
Faraz

0

biến thể dựa trên câu trả lời của @ Pushy , hoạt động trên 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)

Sử dụng:

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

0

Câu trả lời của Tim Ryan đã làm việc cho tôi ... nhưng tôi muốn nó cho Groovy (ví dụ bối cảnh Spock) và 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

Một phiên bản trong Kotlin, trong thuật toán này, tôi đã tạo một trình trang trí cho phép bạn đặt và nhận các biến từ môi trường.

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

Việc triển khai Kotlin gần đây tôi đã thực hiện dựa trên câu trả lời của 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)
    }
}

-12

Bạn có thể truyền tham số vào quy trình java ban đầu của mình với -D:

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

Các giá trị không được biết tại thời điểm thực hiện; chúng được biết đến trong quá trình thực hiện chương trình khi người dùng cung cấp / chọn chúng. Và chỉ thiết lập các thuộc tính hệ thống, không phải các biến môi trường.
Skiphoppy

Sau đó, trong trường hợp đó, bạn có thể muốn tìm một cách thông thường (thông qua tham số args [] cho phương thức chính) để gọi các quy trình con của bạn.
matt b

matt b, cách thông thường là thông qua ProcessBuilder, như đã đề cập trong câu hỏi ban đầu của tôi. :)
Skiphoppy

7
Tham số -D có sẵn thông qua System.getPropertyvà không giống như System.getenv. Ngoài ra, Systemlớp cũng cho phép thiết lập các thuộc tính này một cách tĩnh bằng cách sử dụngsetProperty
anirvan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.