392 votes

Comment définir les variables d'environnement à partir de Java ?

Comment définir les variables d'environnement à partir de Java ? Je vois que je peux le faire pour les sous-processus à l'aide des éléments suivants ProcessBuilder . Mais comme j'ai plusieurs sous-processus à lancer, je préfère modifier l'environnement du processus actuel et laisser les sous-processus en hériter.

Il y a un System.getenv(String) pour obtenir une seule variable d'environnement. Je peux également obtenir une Map de l'ensemble complet des variables d'environnement avec System.getenv() . Mais, en appelant put() sur ce point Map lance un UnsupportedOperationException -- apparemment, ils veulent que l'environnement soit en lecture seule. Et, il n'y a pas de System.setenv() .

Existe-t-il un moyen de définir des variables d'environnement dans le processus en cours d'exécution ? Si oui, comment ? Si non, quel est le raisonnement ? (Est-ce parce que c'est Java et donc que je ne devrais pas faire des choses obsolètes non portables comme toucher à mon environnement) ? Et sinon, avez-vous de bonnes suggestions pour gérer les changements de variables d'environnement que je vais devoir transmettre à plusieurs sous-processus ?

1 votes

System.getEnv() est destiné à être universel, certains environnements n'ont même pas de variables d'environnement.

17 votes

Pour tous ceux qui en ont eu besoin dans le cadre de tests unitaires : stackoverflow.com/questions/8168884/

1 votes

7voto

Paul Blair Points 53

J'ai essayé la réponse de pushy ci-dessus et ça a marché pour la plupart. Cependant, dans certaines circonstances, je verrais bien cette exception :

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

Il s'avère que cela se produit lorsque la méthode a été appelée plus d'une fois, en raison de l'implémentation de certaines classes internes de l'application ProcessEnvironment. Si le setEnv(..) est appelée plus d'une fois, lorsque les clés sont récupérées à partir de la base de données de l'UE. theEnvironment ils sont maintenant des chaînes de caractères (ayant été placées en tant que chaînes de caractères par la première invocation de la fonction setEnv(...) ) et ne peut être converti en type générique de la carte, Variable, qui est une classe interne privée de ProcessEnvironment.

Une version corrigée (en Scala), est ci-dessous. J'espère qu'il ne sera pas trop difficile de la transposer en 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()
  }
}

4voto

skiphoppy Points 16563

En farfouillant sur Internet, il semble qu'il soit possible de faire cela avec JNI. Il faudrait alors faire un appel à putenv() à partir de C, et il faudrait (probablement) le faire d'une manière qui fonctionne à la fois sous Windows et UNIX.

Si tout cela peut être fait, il ne serait sûrement pas trop difficile pour Java lui-même de soutenir cette démarche au lieu de me mettre en camisole de force.

Un ami parlant Perl suggère ailleurs que c'est parce que les variables d'environnement sont globales au processus et que Java s'efforce d'assurer une bonne isolation pour une bonne conception.

2voto

mike rodent Points 482

La réponse de Tim Ryan a fonctionné pour moi... mais je la voulais pour Groovy (contexte Spock par exemple), et 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|"

1voto

GarouDan Points 1561

C'est la version maléfique de Kotlin de celle de @pushy. réponse \=)

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

Cela fonctionne dans macOS Mojave au moins.

1voto

Arun160 Points 11

Je suis tombé sur ce fil de discussion car j'avais une exigence similaire où je devais définir (ou mettre à jour) une variable d'environnement (de façon permanente).

J'ai donc cherché - Comment définir une variable d'environnement de façon permanente à partir de l'invite de commande et c'était très simple !

setx JAVA_LOC C:/Java/JDK

Ensuite, j'ai simplement implémenté la même chose dans mon code Voici ce que j'ai utilisé (supposez - JAVA_LOC est le nom de la variable d'environnement)

        String cmdCommand = "setx JAVA_LOC " + "C:/Java/JDK";
            ProcessBuilder processBuilder = new ProcessBuilder();
            processBuilder.command("cmd.exe", "/c", cmdCommand);
            processBuilder.start();

ProcessBuilder lance un cmd.exe et transmet la commande que vous souhaitez. La variable d'environnement est conservée même si vous arrêtez la JVM/redémarrez le système car elle n'a aucun rapport avec le cycle de vie de la JVM/du programme.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X