IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]


 

28. Le scripting

 

chapitre 2 8

 

Niveau : niveau 4 Supérieur 

 

Le scripting est utilisé depuis longtemps, dans un premier temps, pour automatiser certaines tâches sur des systèmes d'exploitation (exemple le shell sous Linux) puis sous la forme de langages de développement (exemple Perl, Python, ...)

Ces langages n'ont pas pour but de remplacer le langage Java mais ils peuvent avoir une place de choix pour remplir certaines tâches et permettre de bénéficier des points forts de Java et du scripting.

Les langages de scripting possèdent plusieurs caractéristiques qui peuvent être intéressantes :

  • ils sont généralement typés dynamiquement : il n'est pas nécessaire de fournir le type lors de la déclaration d'une variable et la valeur contenue peut changer de type
  • certains peuvent être compilés mais la plupart sont interprétés
  • ils permettent de personnaliser certaines parties d'une application comme la configuration ou les règles métiers

La plate-forme Java permet depuis longtemps d'utiliser des langages de scripts notamment avec des solutions open source comme BeanShell.

Java 6 intègre une API standard, indépendante du langage de scripting utilisé du moment qu'il est compatible avec l'API.

Certains langages de scripting ont été spécifiquement développés pour la JVM : c'est notamment le cas de Groovy.

Ce chapitre contient une section :

 

28.1. L'API Scripting

Java SE 6.0 intègre la possibilité d'utiliser des moteurs de scripting suite à l'intégration des spécifications de la JSR 223.

La JSR 223 a pour but d'intégrer des possibilités de scripting dans les applications Java en permettant :

  • L'intégration de moteurs de scripting
  • La possibilité pour ces moteurs d'accéder à la plate-forme Java
  • L'ajout d'une console permettant l'exécution de scripts en mode ligne de commande (jrunscript)

Les classes et interfaces de cette fonctionnalité sont regroupées dans le package javax.script.

L'API propose un support pour tous les moteurs de scripting compatibles avec elle.

Java SE 6.0 intègre en standard le moteur de scripting Rhino version 1.6 R2 qui propose un support pour le langage Javascript.

La gestion des moteurs utilisables se fait grâce à la classe ScriptEngineManager : elle permet d'obtenir la liste des objets de type ScriptEngineFactory de chaque moteur de scripting installé. Ces méthodes ne sont pas statiques, il est donc nécessaire d'instancier un objet de type ScriptEngineManager pour les utiliser.

 

28.1.1. La mise en oeuvre de l'API

Des fabriques permettent l'instanciation d'un objet de type ScriptEngine qui encapsule le moteur de scripting.

Exemple :
package com.jmd.tests.java6; 

import java.util.List; 
import javax.script.ScriptEngineFactory; 
import javax.script.ScriptEngineManager; 

public class ListerScriptEngine { 

  public static void main(String args[]) { 
    ScriptEngineManager manager = new ScriptEngineManager(); 
    List<ScriptEngineFactory> factories = manager.getEngineFactories(); 
    
    for (ScriptEngineFactory factory : factories) { 
      System.out.println("Name : " + factory.getEngineName()); 
      System.out.println("Version : " + factory.getEngineVersion()); 
      System.out.println("Language name : " + factory.getLanguageName()); 
      System.out.println("Language version : " + factory.getLanguageVersion()); 
      System.out.println("Extensions : " + factory.getExtensions()); 
      System.out.println("Mime types : " + factory.getMimeTypes()); 
      System.out.println("Names : " + factory.getNames()); 
    }
  } 
}

Résultat :
Name : Mozilla Rhino 
Version : 1.6 release 2 
Language name : ECMAScript 
Language version : 1.6 
Extensions : [js] 
Mime types:[application/javascript, application/ecmascript, text/javascript, text/ecmascript] 
Names : [js, rhino, JavaScript, javascript, ECMAScript, ecmascript]

Les propriétés Extensions, MimeType et Names sont importantes car elles sont utilisées pour obtenir une instance de la classe ScriptEngine.

Le ScriptEngineManager permet d'obtenir directement une instance du moteur de scripting à partir d'un nom, d'une extension et d'un type mime particulier respectivement grâce aux méthodes getEngineByName(), getEngineByExtension() et getEngineByMimeType().

Exemple :
package com.jmd.tests.java6;

import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 

public class TestScriptEngine { 
  public static void main(String args[]) { 
    ScriptEngineManager manager = new ScriptEngineManager(); 
    ScriptEngine moteur1 = manager.getEngineByName("rhino"); 
    ScriptEngine moteur2 = manager.getEngineByExtension("js"); 
    ScriptEngine moteur3 = manager.getEngineByName("test"); 
    if (moteur3== null) { 
      System.out.println("Impossible de trouver le moteur test "); 
    } 
  } 
} 

Si aucune fabrique ne correspond au paramètre fourni alors l'instance de type ScriptEngine retournée est null.

 

28.1.2. Ajouter d'autres moteurs de scripting

Il est possible d'ajouter d'autres moteurs de scripting. Le projet scripting hébergé par java.net propose l'encapsulation de nombreux moteurs de scripting pour l'utilisation avec l'API Scripting. https://scripting.dev.java.net/

Il faut télécharger le fichier jsr223-engines.zip. Cette archive contient un répertoire pour chaque moteur. Il faut ajouter le fichier build/xxx-engine.jar au classpath où xxx est le nom du moteur.

Résultat de l'exécution :
Name : Mozilla Rhino 
Version : 1.6 release 2 
Language name : ECMAScript 
Language version : 1.6 
Extensions : [js] 
Mime types:[application/javascript, application/ecmascript, text/javascript, text/ecmascript] 
Names : [js, rhino, JavaScript, javascript, ECMAScript, ecmascript] 
Name : jython 
Version : 2.1 
Language name : python 
Language version : 2.1 
Extensions : [jy, py] 
Mime types : [] 
Names : [jython, python]

Il est nécessaire pour instancier le moteur que celui-ci soit présent dans le classpath. Dans le cas de jython, il faut ajouter le fichier jython.jar dans le classpath. Pour cela, il faut télécharger le fichier jython_Release_2_2alpha1.jar et l'exécuter en double cliquant dessus. Le programme d'installation utilise un assistant :

  • Sur la page « Welcome to Jython », cliquez sur le bouton « Next »
  • Sur la page « Installation type », laissez All sélectionné et cliquez sur Next

  • Sur la page « Version check », cliquez sur Next
  • Sur la page « License agreement », lisez la licence et si vous l'acceptez cliquez sur « I accept » et sur le bouton « Next »
  • Sur la page « Target directory » modifiez le répertoire d'installation au besoin et cliquez sur Next. Si le répertoire n'existe pas, cliquez sur OK puis de nouveau sur le bouton Next
  • Sur la page « Overview (summary of options) », cliquez sur Next pour démarrer l'installation
  • Sur la page « Read me », cliquez sur Next
  • Cliquez sur Finish pour terminer l'installation.

Ajoutez le fichier jython.jar contenu dans le répertoire d'installation au classpath de l'application. Il est alors possible de créer une instance du moteur de script Jython.

Exemple :
package com.jmd.tests.java6; 
 
import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 
 
public class TestJython { 
  public static void main(String args[]) { 
    try { 
      ScriptEngineManager manager = new ScriptEngineManager(); 
      ScriptEngine moteur = manager.getEngineByName("jython"); 
      if (moteur== null) { 
        System.out.println("Impossible de trouver le moteur jython "); 
      } 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
  } 
}

Si le fichier jython.jar n'est pas présent dans le classpath une exception est levée.

Exemple :
Exception in thread "main" java.lang.NoClassDefFoundError: org/python/core/PyObject 
at com.sun.script.jython.JythonScriptEngineFactory.getScriptEngine(
JythonScriptEngineFactory.java:132) 
at javax.script.ScriptEngineManager.getEngineByName(ScriptEngineManager.java:225) 
at com.jmd.tests.java6.TestJython.main(TestJython.java:11)

 

28.1.3. L'évaluation d'un script

La classe ScriptEngine propose plusieurs surcharges de la méthode eval() pour exécuter un script. Ces surcharges attendent en paramètre le script sous la forme d'une chaîne de caractères ou d'un flux de type Reader.

Exemple :
package com.jmd.tests.java6; 
 
import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 
 
public class TestJython { 
  public static void main(String args[]) { 
    try { 
      ScriptEngineManager manager = new ScriptEngineManager(); 
      ScriptEngine moteur = manager.getEngineByName("jython"); 
      if (moteur == null) { 
        System.out.println("Impossible de trouver le moteur jython "); 
      } else { 
        moteur.eval("print \"test\""); 
      } 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
  } 
}

La méthode eval() peut lever une exception de type javax.script.ScriptExceptionsi le moteur détecte une erreur dans le script.

Exemple :
package com.jmd.tests.java6; 
 
import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 
import javax.script.ScriptException; 
 
public class TestRhino { 
  public static void main(String args[]) { 
    ScriptEngineManager manager = new ScriptEngineManager(); 
    ScriptEngine moteur = manager.getEngineByName("rhino"); 
    try { 
      moteur.eval("alert('test');"); 
    } catch (ScriptException e) { 
      e.printStackTrace(); 
    } 
  } 
}

Résultat :
javax.script.ScriptException: sun.org.mozilla.javascript.internal.EcmaError: ReferenceError:
"Alert" n'est pas défini (<Unknown source>#1) in <Unknown source> at line number 1 
at com.sun.script.javascript.RhinoScriptEngine.eval(RhinoScriptEngine.java:110) 
at com.sun.script.javascript.RhinoScriptEngine.eval(RhinoScriptEngine.java:124)
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:247) 
at com.jmd.tests.java6.TestRhino.main(TestRhino.java:13)

Deux surcharges de la méthode eval() attendent en paramètre un objet de type Bindings. C'est une paire clé/valeur qui permet de passer des objets Java au script.

Exemple :
package com.jmd.tests.java6; 

import javax.script.Bindings; 
import javax.script.ScriptContext; 
import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 
import javax.script.ScriptException; 

public class TestBindings { 

  public static void main(String args[]) { 
    ScriptEngineManager manager = new ScriptEngineManager(); 
    ScriptEngine moteur = manager.getEngineByName("rhino"); 
    try { 
      Bindings bindings = moteur.getBindings(ScriptContext.ENGINE_SCOPE); 
      bindings.clear(); 
      bindings.put("entree","valeur"); 
      moteur.eval("var sortie = '';"+ 
        " sortie = entree + ' modifiee '", bindings); 
      String resultat = (String)bindings.get("sortie"); 
      System.out.println("resultat = "+resultat); 
    } catch (ScriptException e) { 
      e.printStackTrace(); 
    } 
  } 
}

Résultat :
resultat = valeur modifiee

La classe ScriptEngine possède deux méthodes pour faciliter l'utilisation des Bindings : les méthodes put() et get() pour respectivement passer un objet au script et obtenir un objet du script.

Exemple :
package com.jmd.tests.java6; 
 
import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 
import javax.script.ScriptException; 
 
public class TestBindings2 { 
  public static void main(String args[]) { 
    ScriptEngineManager manager = new ScriptEngineManager(); 
    ScriptEngine moteur = manager.getEngineByName("rhino"); 
    try { 
      moteur.put("entree","valeur"); 
      moteur.eval("var sortie = '';"+ 
        " sortie = entree + ' modifiee '"); 
      String resultat = (String)moteur.get("sortie"); 
      System.out.println("resultat = "+resultat); 
    } catch (ScriptException e) { 
      e.printStackTrace(); 
    } 
  } 
}

Deux surcharges de la méthode eval() attendent en paramètre un objet de type ScriptContext qui permet de préciser la portée des Bindings.

Il existe deux portées prédéfinies :

  • ScriptContext.GLOBAL_SCOPE : portée pour tous les moteurs
  • ScriptContext.ENGINE_SCOPE : portée pour le moteur courant uniquement

Il est possible de préciser le contexte par défaut du moteur en utilisant la méthode SetContext() de la classe ScriptEngine.

La méthode getBindings() permet d'obtenir les bindings pour la portée fournie en paramètre.

 

28.1.4. L'interface Compilable

Les scripts sont généralement interprétés : ils doivent donc être lus, validés et évalués avant d'être exécutés. Ces opérations peuvent être coûteuses en ressources et en temps.

L'interface Compilable propose de compiler ces scripts afin de rendre leurs exécutions plus rapides. L'implémentation de cette interface par un moteur de scripting est optionnelle : il faut donc vérifier que l'instance du moteur de scripting implémente cette interface et la caster vers le type Compilable avant d'utiliser cette fonctionnalité.

La méthode compile() réalise une compilation du script et retourne un objet de type CompiledScript en cas de succès.

Le script compilé est exécuté avec la méthode eval() de la classe CompiledScript.

Exemple :
package com.jmd.tests.java6; 

import javax.script.Bindings; 
import javax.script.Compilable; 
import javax.script.CompiledScript; 
import javax.script.ScriptContext; 
import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 
import javax.script.ScriptException; 

public class TestCompilable { 

  public static void main(String args[]) { 
    ScriptEngineManager manager = new ScriptEngineManager(); 
    ScriptEngine moteur = manager.getEngineByName("rhino"); 
    try { 
      Bindings bindings = moteur.getBindings(ScriptContext.ENGINE_SCOPE); 
      bindings.clear(); 
      bindings.put("compteur", 1); 
      
      if (moteur instanceof Compilable) { 
        Compilable moteurCompilable = (Compilable) moteur; 
        CompiledScript scriptCompile = moteurCompilable 
          .compile("var sortie = '';" 
          + "sortie = 'chaine' + compteur;" 
          + "compteur++;"); 
        
        for (int i = 1; i < 11; i++) { 
          scriptCompile.eval(bindings); 
          String resultat = (String) bindings.get("sortie"); 
          System.out.println("valeur " + i + " = " + resultat); 
        } 
      } else { 
      System.err 
        .println("Le moteur n'implemente pas l'interface Compilable"); 
      } 
    } catch (ScriptException e) { 
      e.printStackTrace(); 
    } 
  } 
}

Résultat :
valeur 1 = chaine1
valeur 2 = chaine2
valeur 3 = chaine3
valeur 4 = chaine4
valeur 5 = chaine5
valeur 6 = chaine6
valeur 7 = chaine7
valeur 8 = chaine8
valeur 9 = chaine9
valeur 10 = chaine10

L'utilisation de cette fonctionnalité est particulièrement intéressante pour des exécutions répétées du script.

 

28.1.5. L'interface Invocable

Cette interface permet d'invoquer une fonction définie dans le code source du script.

Dès qu'une fonction a été évaluée par le moteur de scripting, elle peut être invoquée grâce à la méthode invoke() de l'interface Invocable. L'implémentation de cette interface par un moteur de scripting est optionnelle : il faut donc vérifier avant d'utiliser cette fonctionnalité si le moteur implémente cette interface.

Il est possible de fournir des paramètres à la fonction invoquée.

Exemple :
package com.jmd.tests.java6; 

import javax.script.Bindings; 
import javax.script.Invocable; 
import javax.script.ScriptContext; 
import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 
import javax.script.ScriptException; 

public class TestInvocable { 

  public static void main(String args[]) { 
    ScriptEngineManager manager = new ScriptEngineManager(); 
    ScriptEngine moteur = manager.getEngineByName("rhino"); 
    try { 
    
      moteur.eval("function afficher(valeur) {" 
        + "var sortie = '';" 
        + "sortie = 'chaine' + valeur;" 
        + "return sortie;" 
        + "}"); 
      
      if (moteur instanceof Invocable) { 
        Invocable moteurInvocable = (Invocable) moteur; 
        Object resultat = moteurInvocable.invokeFunction("afficher", 
          new Integer(10)); 
        System.out.println("resultat = " + resultat); 
      } else { 
        System.err.println("Le moteur n'implemente pas l'interface Invocable"); 
      } 
    
    } catch (ScriptException e) { 
      e.printStackTrace(); 
    } catch (NoSuchMethodException e) { 
      e.printStackTrace(); 
    } 
  } 
}

Résultat :
resultat = chaine10

La méthode getInterface() de l'interface Invocable permet d'obtenir dynamiquement un objet dont la ou les méthodes sont codées dans le script.

L'exemple ci-dessous va définir une fonction run() qui sera invoquée dans un thread en utilisant la méthode getInterface() avec en paramètre un objet de type Class qui encapsule la classe Runnable

Exemple :
package com.jmd.tests.java6; 

import javax.script.Invocable; 
import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 
import javax.script.ScriptException; 
 
public class TestInvocable2 { 
 
  public static void main(String args[]) { 
    ScriptEngineManager manager = new ScriptEngineManager(); 
    ScriptEngine moteur = manager.getEngineByName("rhino"); 
    try { 
  
      moteur.eval("function run(){" 
        + "for (i = 0 ; i < 1000 ; i ++) {" 
        + "print('run'+i);" 
        + "}" 
        + "}"); 
      
      if (moteur instanceof Invocable) { 
        Invocable moteurInvocable = (Invocable) moteur; 
        Runnable runnable = moteurInvocable.getInterface(Runnable.class); 
        Thread thread = new Thread(runnable); 
        thread.start(); 
        for (int i = 0; i < 1000; i++) { 
          System.out.println("main" + i); 
        } 
        thread.join(); 
      } else { 
        System.err.println("Le moteur n'implemente pas l'interface Invocable"); 
      } 
      
    } catch (ScriptException e) { 
      e.printStackTrace(); 
    } catch (InterruptedException e) { 
      e.printStackTrace(); 
    } 
  } 
}

Extrait du résultat :
main988 
run174run175main989 
main990 
main991 
main992 
main993 
main994 
main995 
main996 
main997 
main998 
main999 
run176run177run178run179run180

L'itération du programme s'exécute beaucoup plus rapidement que le thread : l'appel à la méthode join() du thread permet d'attendre la fin de son exécution avant de terminer l'application.

 

28.1.6. La commande jrunscript

La commande jrunscript est un outil du JDK utilisable en ligne de commande qui permet d'exécuter des scripts.

Remarque : pour pouvoir utiliser cette commande, il est nécessaire d'ajouter dans le path le chemin du répertoire bin du JDK.

Les options -help et -? permettent d'obtenir la liste des options de la commande.
L'option -q permet de connaître la liste des moteurs de scripting utilisables.
Les options -cp et -classpath permettent de préciser le classpath qui sera utilisé.

Si seul le moteur de scripting par défaut est installé alors il n'est pas utile de préciser le moteur à utiliser. Dans le cas contraire, il est nécessaire de préciser le moteur à utiliser grâce à l'option -l

Exemple :
C:\>jrunscript -q
Language ECMAScript 1.6 implemention "Mozilla Rhino" 1.6 release 2

Sans autre argument, la commande jrunscript affiche un prompt qui permet de saisir le script à évaluer.

Exemple :
C:\>jrunscript
js> var i = 10* 10;
js> i;
100.0
js> i
100.0
js> i++;
100.0
js> i
101.0
js> print i;
script error: sun.org.mozilla.javascript.internal.EvaluatorException: il manque
';' avant une instruction (<STDIN>#1) in <STDIN> at line number 1
js>

 


Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]