Bagaimana cara mengatur variabel lingkungan dari Java?


289

Bagaimana cara mengatur variabel lingkungan dari Java? Saya melihat bahwa saya dapat melakukan ini untuk menggunakan subproses ProcessBuilder. Saya memiliki beberapa subproses untuk memulai, jadi saya lebih suka memodifikasi lingkungan proses saat ini dan membiarkan subproses mewarisinya.

Ada System.getenv(String)untuk mendapatkan variabel lingkungan tunggal. Saya juga bisa mendapatkan satu Mapset variabel lingkungan lengkap dengan System.getenv(). Tapi, memanggil put()itu Mapmelempar UnsupportedOperationException- tampaknya mereka bermaksud agar lingkungan hanya dibaca. Dan tidak ada System.setenv().

Jadi, apakah ada cara untuk mengatur variabel lingkungan dalam proses yang sedang berjalan? Jika ya, bagaimana caranya? Jika tidak, apa alasannya? (Apakah ini karena Jawa dan karena itu saya tidak boleh melakukan hal-hal usang yang tidak dapat ditiru seperti menyentuh lingkungan saya?) Dan jika tidak, ada saran bagus untuk mengelola perubahan variabel lingkungan yang akan perlu saya beri makan ke beberapa subproses?


System.getEnv () dimaksudkan sebagai universal-ish, beberapa lingkungan bahkan tidak memiliki variabel lingkungan.
b1nary.atr0phy

7
Bagi siapa pun yang membutuhkan ini untuk kasus penggunaan unit testing: stackoverflow.com/questions/8168884/…
Atifm

Jawaban:


88

(Apakah ini karena ini adalah Jawa dan karenanya saya tidak boleh melakukan hal-hal usang yang tidak dapat dipercaya seperti menyentuh lingkungan saya?)

Saya pikir Anda telah memukul paku di kepala.

Cara yang mungkin untuk meringankan beban adalah dengan memfaktorkan suatu metode

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

dan melewati apa pun ProcessBuildermelalui itu sebelum memulai mereka.

Juga, Anda mungkin sudah mengetahui hal ini, tetapi Anda dapat memulai lebih dari satu proses dengan proses yang sama ProcessBuilder. Jadi jika subproses Anda sama, Anda tidak perlu melakukan pengaturan ini berulang-ulang.


1
Sayang sekali manajemen tidak akan membiarkan saya menggunakan bahasa portabel yang berbeda untuk menjalankan rangkaian proses jahat dan usang ini. :)
skiphoppy

18
S.Lott, saya tidak ingin mengatur lingkungan orangtua. Saya ingin mengatur lingkungan saya sendiri.
skiphoppy

3
Itu bekerja dengan baik, kecuali perpustakaan orang lain (misalnya Sun) yang meluncurkan proses.
sullivan-

24
@ b1naryatr0phy Anda melewatkan intinya. Tidak ada yang bisa bermain dengan variabel lingkungan Anda karena variabel tersebut bersifat lokal untuk suatu proses (apa yang Anda atur di Windows adalah nilai default). Setiap proses bebas untuk mengubah variabelnya sendiri ... kecuali Java-nya.
maaartinus

9
Keterbatasan java ini sedikit keluar. Tidak ada alasan untuk java tidak membiarkan Anda mengatur env vars selain "karena kami tidak ingin java melakukan ini".
IanNorton

232

Untuk digunakan dalam skenario di mana Anda perlu menetapkan nilai lingkungan spesifik untuk pengujian unit, Anda mungkin menemukan hack berikut berguna. Ini akan mengubah variabel lingkungan di seluruh JVM (jadi pastikan Anda mengatur ulang setiap perubahan setelah pengujian Anda), tetapi tidak akan mengubah lingkungan sistem Anda.

Saya menemukan bahwa kombinasi dari dua peretasan kotor oleh Edward Campbell dan anonim berfungsi paling baik, karena salah satu dari itu tidak bekerja di bawah linux, satu tidak bekerja di bawah windows 7. Jadi untuk mendapatkan peretas jahat multiplatform saya menggabungkannya:

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

Ini Bekerja seperti pesona. Penghargaan penuh untuk dua penulis peretasan ini.


1
Apakah ini hanya akan berubah dalam memori, atau benar-benar mengubah seluruh variabel lingkungan dalam sistem?
Shervin Asgari

36
Ini hanya akan mengubah variabel lingkungan dalam memori. Ini bagus untuk pengujian, karena Anda dapat mengatur variabel lingkungan yang diperlukan untuk pengujian Anda, tetapi biarkan envs dalam sistem apa adanya. Bahkan, saya akan sangat menyarankan siapa pun untuk tidak menggunakan kode ini untuk tujuan lain selain pengujian. Kode ini jahat ;-)
memaksa

9
Sebagai FYI, JVM membuat salinan variabel lingkungan ketika dimulai. Ini akan mengedit salinan itu, bukan variabel lingkungan untuk proses induk yang memulai JVM.
bmeding

Saya mencoba ini di Android dan sepertinya tidak perlu. Adakah yang beruntung di Android?
Hans-Christoph Steiner

5
Tentu,import java.lang.reflect.Field;
memaksa

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

Atau untuk menambah / memperbarui satu var dan menghapus loop sesuai saran 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
Kedengarannya seperti itu akan mengubah peta dalam memori, tetapi apakah itu menyimpan nilai ke sistem?
Jon Onstott

1
baik itu memang mengubah peta memori variabel lingkungan. Saya kira itu sudah cukup dalam banyak kasus penggunaan. @Edward - astaga, sulit membayangkan bagaimana solusi ini dipikirkan sejak awal!
anirvan

13
Ini tidak akan mengubah variabel lingkungan pada sistem, tetapi akan mengubahnya dalam permintaan Java saat ini. Ini sangat berguna untuk pengujian unit.
Stuart K

10
mengapa tidak menggunakan Class<?> cl = env.getClass();itu untuk loop?
thejoshwolfe

1
Inilah yang sebenarnya saya cari! Saya telah menulis tes integrasi untuk beberapa kode yang menggunakan alat pihak ketiga yang, untuk beberapa alasan, hanya memungkinkan Anda memodifikasi panjang batas waktu standar pendek yang tidak masuk akal dengan variabel lingkungan.
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

pada Android antarmuka diekspos melalui Libcore.os sebagai semacam API tersembunyi.

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

Kelas Libcore serta OS antarmuka publik. Hanya deklarasi kelas yang hilang dan perlu ditunjukkan ke tautan. Tidak perlu menambahkan kelas ke aplikasi, tetapi juga tidak ada salahnya jika disertakan.

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
Diuji dan bekerja pada Android 4.4.4 (CM11). PS Satu-satunya penyesuaian yang saya lakukan adalah mengganti throws ErrnoExceptiondengan throws Exception.
DavisNT

7
API 21, miliki Os.setEnvsekarang. developer.android.com/reference/android/system/… , java.lang.String, boolean)
Jared Burrows

1
Berpotensi mati sekarang dengan batasan baru Pie: developer.android.com/about/versions/pie/…
TWiStErRob

13

Hanya Linux

Mengatur variabel lingkungan tunggal (berdasarkan jawaban oleh 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);
    }
}

Pemakaian:

Pertama, letakkan metode di kelas yang Anda inginkan, misalnya SystemUtil. Kemudian panggil secara statis:

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

Jika Anda menelepon System.getenv("SHELL")setelah ini, Anda akan "/bin/bash"kembali.


Di atas tidak berfungsi di windows 10, tetapi akan bekerja di linux.
mengchengfeng

Menarik. Saya tidak mencobanya sendiri di Windows. Apakah Anda mendapatkan kesalahan, @mengchengfeng?
Hubert Grzeskowiak

@HubertGrzeskowiak Kami tidak melihat pesan kesalahan, hanya saja tidak berhasil ...
mengchengfeng

9

Ini adalah kombinasi dari jawaban @ paul-blair yang dikonversi ke Java yang mencakup beberapa pembersihan yang ditunjukkan oleh paul blair dan beberapa kesalahan yang tampaknya ada di dalam kode @pushy yang terdiri dari @Edward Campbell dan anonim.

Saya tidak bisa menekankan seberapa banyak kode ini HANYA harus digunakan dalam pengujian dan sangat peretasan. Tetapi untuk kasus-kasus di mana Anda memerlukan pengaturan lingkungan dalam pengujian, itulah yang saya butuhkan.

Ini juga termasuk beberapa sentuhan kecil milik saya yang memungkinkan kode bekerja pada kedua Windows yang sedang berjalan

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)

serta Centos berjalan

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)

Pelaksanaan:

/**
 * 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

Ternyata solusi dari @ pushy / @ anonymous / @ Edward Campbell tidak berfungsi di Android karena Android sebenarnya bukan Java. Secara khusus, Android tidak memiliki java.lang.ProcessEnvironmentsama sekali. Tapi ternyata lebih mudah di Android, Anda hanya perlu melakukan panggilan JNI ke POSIX setenv():

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

Dan di Jawa:

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

Seperti kebanyakan orang yang telah menemukan utas ini, saya sedang menulis beberapa unit test dan perlu memodifikasi variabel lingkungan untuk menetapkan kondisi yang benar untuk menjalankan tes. Namun, saya menemukan jawaban yang paling banyak dipilih memiliki beberapa masalah dan / atau sangat samar atau terlalu rumit. Semoga ini akan membantu orang lain untuk memilah solusi lebih cepat.

Pertama, saya akhirnya menemukan solusi @ Hubert Grzeskowiak sebagai solusi paling sederhana dan berhasil bagi saya. Saya berharap saya akan datang ke yang pertama. Ini didasarkan pada jawaban @Edward Campbell, tetapi tanpa menyulitkan untuk pencarian loop.

Namun, saya mulai dengan solusi @ pushy, yang mendapat sebagian besar upvotes. Ini adalah kombinasi dari @anonymous dan @Edward Campbell's. @pushy mengklaim kedua pendekatan diperlukan untuk mencakup lingkungan Linux dan Windows. Saya menjalankan OS X dan menemukan bahwa keduanya berfungsi (sekali masalah dengan pendekatan @anonymous diperbaiki). Seperti yang telah dicatat orang lain, solusi ini bekerja sebagian besar waktu, tetapi tidak semua.

Saya pikir sumber dari sebagian besar kebingungan berasal dari solusi @ anonim yang beroperasi di bidang 'theEnvironment'. Melihat definisi ProcessEnvironment struktur , 'theEnvironment' bukanlah Map <String, String> melainkan Map <Variable, Value>. Mengosongkan peta berfungsi dengan baik, tetapi operasi putAll membangun kembali peta sebuah Peta <String, String>, yang berpotensi menyebabkan masalah ketika operasi selanjutnya beroperasi pada struktur data menggunakan API normal yang mengharapkan Peta <Variable, Value>. Juga, mengakses / menghapus elemen individual adalah masalah. Solusinya adalah mengakses 'theEnvironment' secara tidak langsung melalui 'theUnmnable Ennvironment'. Tapi karena ini adalah tipe UnmodifiableMap , akses harus dilakukan melalui variabel pribadi 'm' dari tipe UnmodifiableMap. Lihat getModifiableEnvironmentMap2 dalam kode di bawah ini.

Dalam kasus saya, saya perlu menghapus beberapa variabel lingkungan untuk pengujian saya (yang lain harus tidak berubah). Kemudian saya ingin mengembalikan variabel lingkungan ke keadaan sebelumnya setelah pengujian. Rutinitas di bawah ini membuatnya mudah untuk dilakukan. Saya menguji kedua versi getModifiableEnvironmentMap pada OS X, dan keduanya bekerja dengan setara. Meskipun berdasarkan komentar di utas ini, yang satu mungkin merupakan pilihan yang lebih baik daripada yang lain tergantung pada lingkungan.

Catatan: Saya tidak memasukkan akses ke 'theCaseInsensitiveEnvironmentField' karena itu sepertinya Windows khusus dan saya tidak punya cara untuk mengujinya, tetapi menambahkan itu harus lurus ke depan.

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

Terima kasih, itu persis kasus penggunaan saya dan di bawah mac os x juga.
Rafael Gonçalves

Sangat menyukai ini sehingga saya membuat versi yang sedikit lebih sederhana untuk Groovy, lihat di bawah.
mike rodent

4

Mengaduk-aduk online, sepertinya mungkin untuk melakukan ini dengan JNI. Anda kemudian harus melakukan panggilan ke putenv () dari C, dan Anda (mungkin) harus melakukannya dengan cara yang bekerja pada Windows dan UNIX.

Jika semua itu bisa dilakukan, tentu tidak akan terlalu sulit bagi Jawa sendiri untuk mendukung ini daripada menempatkan saya dalam jaket lurus.

Teman Perl yang berbicara di tempat lain menyarankan bahwa ini karena variabel lingkungan adalah proses global dan Java berusaha keras untuk isolasi yang baik untuk desain yang baik.


Ya, Anda dapat mengatur lingkungan proses dari kode C. Tapi saya tidak akan mengandalkan itu bekerja di Jawa. Ada peluang bagus bahwa JVM menyalin lingkungan ke objek Java String selama startup, sehingga perubahan Anda tidak akan digunakan untuk operasi JVM di masa depan.
Darron

Terima kasih atas peringatannya, Darron. Mungkin ada peluang bagus Anda benar.
skiphoppy

2
@Darron banyak alasan mengapa orang ingin melakukan ini tidak ada hubungannya sama sekali dengan apa yang menurut JVM lingkungan. (Pikirkan pengaturan LD_LIBRARY_PATHsebelum memanggil Runtime.loadLibrary(); dlopen()panggilan itu memanggil melihat lingkungan nyata , bukan pada ide Jawa yang sama).
Charles Duffy

Ini berfungsi untuk subproses yang dimulai oleh perpustakaan asli (yang dalam kasus saya kebanyakan dari mereka), tetapi sayangnya tidak berfungsi untuk subproses yang dimulai oleh kelas Java Process atau ProcessBuilder.
Dan

4

Mencoba jawaban pushy di atas dan itu berhasil sebagian besar. Namun, dalam kondisi tertentu, saya akan melihat pengecualian ini:

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

Ini ternyata terjadi ketika metode dipanggil lebih dari sekali, karena implementasi kelas-kelas dalam tertentu. ProcessEnvironment.Jika setEnv(..)metode dipanggil lebih dari sekali, ketika kunci diambil dari theEnvironmentpeta, mereka sekarang adalah string (telah dimasukkan ke dalam sebagai string oleh doa pertama setEnv(...)) dan tidak dapat dilemparkan ke tipe generik peta, Variable,yang merupakan kelas dalam privat dariProcessEnvironment.

Versi tetap (dalam Scala), di bawah. Semoga tidak terlalu sulit untuk dibawa ke Jawa.

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

Di mana JavaClass didefinisikan?
Mike Slinn

1
Agaknya import java.lang.{Class => JavaClass}.
Randall Whitman

1
Implementasi java.lang.ProcessEnvironment berbeda pada platform yang berbeda bahkan untuk build yang sama. Sebagai contoh, tidak ada kelas java.lang.ProcessEnvironment $ Variabel dalam implementasi Windows tetapi kelas ini ada dalam satu untuk Linux. Anda dapat dengan mudah memeriksanya. Cukup unduh tar.gz distribusi JDK untuk Linux dan ekstrak sumbernya dari src.zip kemudian bandingkan dengan file yang sama dari distribusi untuk Windows. Mereka sama sekali berbeda di JDK 1.8.0_181. Saya belum memeriksanya di Java 10 tetapi saya tidak akan terkejut jika ada gambar yang sama.
Alex Konshin

1

Ini adalah versi jahat Kotlin dari jawaban jahat @ 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)
            }
        }
    }

Ini berfungsi di MacOS Mojave setidaknya.


0

Jika Anda bekerja dengan SpringBoot Anda bisa menambahkan dengan menentukan variabel lingkungan di properti berikut:

was.app.config.properties.toSystemProperties

1
Bisakah Anda jelaskan sedikit?
Faraz

0

varian berdasarkan jawaban @ pushy , bekerja di 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)

Pemakaian:

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

0

Jawaban Tim Ryan bekerja untuk saya ... tapi saya menginginkannya untuk Groovy (konteks Spock misalnya), dan 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

Versi di Kotlin, dalam algoritma ini saya membuat dekorator yang memungkinkan Anda untuk mengatur dan mendapatkan variabel dari lingkungan.

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

Implementasi Kotlin yang baru-baru ini saya buat berdasarkan jawaban 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

Anda bisa mengirimkan parameter ke proses java awal Anda dengan -D:

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

Nilai tidak diketahui pada waktu eksekusi; mereka menjadi dikenal selama eksekusi program ketika pengguna menyediakan / memilih mereka. Dan itu hanya menetapkan properti sistem, bukan variabel lingkungan.
skiphoppy

Kemudian dalam hal ini Anda mungkin ingin menemukan cara biasa (melalui parameter args [] ke metode utama) untuk memanggil subproses Anda.
matt b

matt b, cara biasa adalah melalui ProcessBuilder, seperti yang disebutkan dalam pertanyaan awal saya. :)
skiphoppy

7
Parameter -D tersedia melalui System.getPropertydan tidak sama dengan System.getenv. Selain itu, Systemkelas ini juga memungkinkan untuk mengatur properti ini secara statis menggunakansetProperty
anirvan
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.