|
|
|
|
|
|
Développons en Java v 1.60 | |
| Copyright (C) 1999-2011 Jean-Michel DOUDOUX |
![]() |
![]() |
![]() |
Ce chapitre contient plusieurs sections :
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 :
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 compatible 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 via 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.
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.
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 ou 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 :


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) |
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.ScriptException si une erreur est détectée par le moteur 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() attendant en paramètre un objet de type Bindings. C'est un objet de type Map 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é des Bindings.
Il existe deux portées prédéfinies :
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.
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 leur prochaine exécution plus rapide. 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 le caster vers le type Compilable avant d'utiliser ces fonctionnalités.
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.
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 ces fonctionnalités 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.
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 1.60 | ||
| Copyright (C) 1999-2011 Jean-Michel DOUDOUX |