Table of Contents
Integration de scripts JavaScript avec Java : une approche pratique
Introduction
L’integration de scripts JavaScript avec Java est un sujet essentiel pour les developpeurs qui souhaitent exploiter les avantages des deux langages. Nashorn, le moteur JavaScript integre a Java 8, permet la prise en charge de scripts JavaScript dans n’importe quelle application Java. Ce moteur haute performance offre une interoperabilite transparente entre Java et JavaScript, permettant aux developpeurs de tirer parti de la flexibilite de JavaScript tout en conservant la robustesse de Java.
Dans cet article approfondi, nous allons explorer en detail comment utiliser les variables globales avec Nashorn. Vous apprendrez a definir des variables depuis Java, a les utiliser dans vos scripts JavaScript, et a recuperer les resultats. Nous couvrirons egalement les bonnes pratiques et les pieges courants a eviter.
Pourquoi utiliser Nashorn ?
Nashorn presente plusieurs avantages majeurs :
- Performance : Compile JavaScript en bytecode Java pour une execution rapide
- Interoperabilite : Acces direct aux classes et objets Java depuis JavaScript
- Simplicite : API intuitive via
javax.script - Securite : Controle fin sur l’execution des scripts
Note importante : Nashorn a ete deprecie dans Java 11 et supprime dans Java 15. Pour les projets modernes, considerez GraalJS comme alternative. Cependant, Nashorn reste pertinent pour la maintenance de projets existants sur Java 8-10.
Configuration de l’Environnement Nashorn
Avant de travailler avec les variables globales, configurons correctement notre environnement Nashorn :
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.Bindings;
import javax.script.ScriptContext;
public class NashornSetup {
private ScriptEngine engine;
public NashornSetup() {
ScriptEngineManager manager = new ScriptEngineManager();
engine = manager.getEngineByName("nashorn");
if (engine == null) {
throw new RuntimeException("Nashorn engine not available. " +
"Ensure you're using Java 8-14.");
}
}
public ScriptEngine getEngine() {
return engine;
}
}
Definition des Variables Globales
Pour commencer, nous devons definir une variable globale dans le script JavaScript. Pour cela, nous utilisons la methode put() de l’objet ScriptEngine. Cette methode place une variable dans le scope global du moteur de script.
Methode Simple avec put()
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
// Definition d'une variable String
engine.put("textToPrint", "Donnees definies en Java.");
// Definition de variables de differents types
engine.put("compteur", 42);
engine.put("estActif", true);
engine.put("prix", 19.99);
Dans cet exemple, nous definissons plusieurs variables de types differents qui seront accessibles dans le script JavaScript.
Utilisation des Bindings pour un Controle Avance
Les Bindings offrent un controle plus fin sur la portee des variables :
// Creer des bindings personnalises
Bindings bindings = engine.createBindings();
bindings.put("nom", "Jean");
bindings.put("age", 30);
bindings.put("ville", "Paris");
// Appliquer les bindings au scope ENGINE
engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
// Ou utiliser les bindings globaux (partages entre moteurs)
Bindings globalBindings = engine.getBindings(ScriptContext.GLOBAL_SCOPE);
if (globalBindings != null) {
globalBindings.put("appVersion", "1.0.0");
}
Passer des Objets Java Complexes
Vous pouvez egalement passer des objets Java complexes :
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.List;
// Passer une Map
Map<String, Object> config = new HashMap<>();
config.put("maxConnections", 100);
config.put("timeout", 30000);
config.put("debug", false);
engine.put("config", config);
// Passer une List
List<String> utilisateurs = new ArrayList<>();
utilisateurs.add("Alice");
utilisateurs.add("Bob");
utilisateurs.add("Charlie");
engine.put("utilisateurs", utilisateurs);
// Execution du script utilisant ces objets
engine.eval("print('Max connections: ' + config.get('maxConnections'));");
engine.eval("print('Premier utilisateur: ' + utilisateurs.get(0));");
Execution de Scripts JavaScript
Une fois que nous avons defini les variables globales, nous pouvons executer le script JavaScript. Pour cela, nous utilisons la methode eval() de l’objet ScriptEngine.
Execution Simple
try {
// Utiliser une variable definie en Java
engine.eval("print(textToPrint);");
// Scripts multi-lignes
String script = """
var message = 'Bonjour ' + nom + '!';
var anneeNaissance = 2024 - age;
print(message);
print('Annee de naissance estimee: ' + anneeNaissance);
""";
engine.eval(script);
} catch (ScriptException ex) {
System.err.println("Erreur de script: " + ex.getMessage());
System.err.println("Ligne: " + ex.getLineNumber());
System.err.println("Colonne: " + ex.getColumnNumber());
}
Execution avec Retour de Valeur
// Recuperer une valeur calculee par JavaScript
Object resultat = engine.eval("40 + 2");
System.out.println("Resultat: " + resultat); // 42
// Recuperer un objet complexe
engine.eval("var personne = { nom: 'Alice', age: 25 };");
Object personne = engine.get("personne");
// Acceder aux proprietes de l'objet JavaScript
if (personne instanceof jdk.nashorn.api.scripting.ScriptObjectMirror) {
jdk.nashorn.api.scripting.ScriptObjectMirror mirror =
(jdk.nashorn.api.scripting.ScriptObjectMirror) personne;
System.out.println("Nom: " + mirror.get("nom"));
System.out.println("Age: " + mirror.get("age"));
}
Utilisation d’Objets Java dans JavaScript
Nashorn permet l’utilisation bidirectionnelle : vous pouvez non seulement passer des objets Java a JavaScript, mais aussi acceder a des classes Java depuis vos scripts.
Recuperer des Valeurs depuis JavaScript
// Definir une variable en JavaScript
engine.eval("var resultatCalcul = 'Traitement termine avec succes';");
// Recuperer la valeur en Java
String value = (String) engine.get("resultatCalcul");
System.out.println(value); // "Traitement termine avec succes"
// Recuperer des types numeriques
engine.eval("var score = 95.5;");
Double score = (Double) engine.get("score");
System.out.println("Score: " + score);
Acceder aux Classes Java depuis JavaScript
String script = """
// Importer des classes Java
var ArrayList = Java.type('java.util.ArrayList');
var HashMap = Java.type('java.util.HashMap');
var System = Java.type('java.lang.System');
// Creer et utiliser des objets Java
var liste = new ArrayList();
liste.add('Premier');
liste.add('Deuxieme');
liste.add('Troisieme');
// Afficher via System.out
System.out.println('Taille de la liste: ' + liste.size());
// Retourner la liste a Java
liste;
""";
Object result = engine.eval(script);
List<String> listeJava = (List<String>) result;
Prise en Charge des Interfaces Java
Nashorn prend en charge les interfaces Java, notamment les interfaces fonctionnelles. Cela permet d’implementer des interfaces Java directement en JavaScript.
Definition d’une Interface Fonctionnelle
@FunctionalInterface
public interface Calculateur {
double calculer(double a, double b);
}
public interface Pet {
void eat();
void sleep();
String getName();
}
Implementation en JavaScript
// Interface fonctionnelle - syntaxe simplifiee
engine.eval("var addition = function(a, b) { return a + b; };");
Calculateur calc = (Calculateur) engine.get("addition");
System.out.println("5 + 3 = " + calc.calculer(5, 3));
// Interface avec plusieurs methodes
String petScript = """
var monChat = new Pet() {
eat: function() { print('Miam miam!'); },
sleep: function() { print('Zzz...'); },
getName: function() { return 'Felix'; }
};
monChat;
""";
// Note: Definir l'interface Pet dans le scope
engine.put("Pet", Pet.class);
Pet pet = (Pet) engine.eval(petScript);
pet.eat(); // Affiche: Miam miam!
pet.sleep(); // Affiche: Zzz...
System.out.println("Nom: " + pet.getName()); // Felix
Exemple Complet : Application Pratique
Voici un exemple complet et fonctionnel montrant toutes les techniques abordees :
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.Invocable;
import java.util.HashMap;
import java.util.Map;
public class NashornGlobalVariablesDemo {
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
if (engine == null) {
System.err.println("Nashorn non disponible!");
return;
}
try {
// 1. Definition des variables globales
engine.put("appName", "MonApplication");
engine.put("version", "2.0.0");
engine.put("debugMode", true);
// 2. Passer un objet de configuration
Map<String, Object> config = new HashMap<>();
config.put("maxUsers", 1000);
config.put("timeout", 30000);
engine.put("config", config);
// 3. Definir des fonctions JavaScript
String functions = """
function formatMessage(prefix, message) {
return '[' + prefix + '] ' + message;
}
function processConfig() {
var result = {
app: appName,
ver: version,
debug: debugMode,
maxUsers: config.get('maxUsers')
};
return JSON.stringify(result);
}
""";
engine.eval(functions);
// 4. Appeler les fonctions JavaScript depuis Java
Invocable invocable = (Invocable) engine;
String message = (String) invocable.invokeFunction(
"formatMessage", "INFO", "Application demarree"
);
System.out.println(message);
String configJson = (String) invocable.invokeFunction("processConfig");
System.out.println("Config: " + configJson);
// 5. Recuperer des variables modifiees
engine.eval("var resultat = 'Traitement OK';");
System.out.println("Resultat: " + engine.get("resultat"));
} catch (ScriptException e) {
System.err.println("Erreur de script: " + e.getMessage());
} catch (NoSuchMethodException e) {
System.err.println("Methode non trouvee: " + e.getMessage());
}
}
}
Sortie attendue :
[INFO] Application demarree
Config: {"app":"MonApplication","ver":"2.0.0","debug":true,"maxUsers":1000}
Resultat: Traitement OK
Bonnes Pratiques
Suivez ces recommandations pour une utilisation optimale de Nashorn avec les variables globales :
1. Gestion des Erreurs Robuste
public Object executeScript(ScriptEngine engine, String script) {
try {
return engine.eval(script);
} catch (ScriptException e) {
// Logger l'erreur avec contexte
System.err.printf("Erreur ligne %d, colonne %d: %s%n",
e.getLineNumber(),
e.getColumnNumber(),
e.getMessage());
// Retourner une valeur par defaut ou relancer
return null;
}
}
2. Reutiliser les Instances ScriptEngine
// BIEN: Reutiliser l'engine pour plusieurs evaluations
public class ScriptService {
private final ScriptEngine engine;
public ScriptService() {
ScriptEngineManager manager = new ScriptEngineManager();
this.engine = manager.getEngineByName("nashorn");
}
public Object evaluate(String script) throws ScriptException {
return engine.eval(script);
}
}
// MAL: Creer un nouvel engine a chaque fois (couteux)
public Object evaluateBad(String script) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager()
.getEngineByName("nashorn");
return engine.eval(script);
}
3. Isolation des Contextes
// Utiliser des Bindings separes pour isoler les executions
public Object evaluateIsolated(ScriptEngine engine, String script,
Map<String, Object> variables) throws ScriptException {
Bindings bindings = engine.createBindings();
bindings.putAll(variables);
return engine.eval(script, bindings);
}
4. Validation des Entrees
public void setVariable(ScriptEngine engine, String name, Object value) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Le nom de variable ne peut etre vide");
}
if (!name.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
throw new IllegalArgumentException("Nom de variable invalide: " + name);
}
engine.put(name, value);
}
Pieges Courants
Evitez ces erreurs frequentes lors de l’utilisation de Nashorn :
1. Oublier de Verifier la Disponibilite du Moteur
// PROBLEME: NullPointerException si Nashorn n'est pas disponible
ScriptEngine engine = manager.getEngineByName("nashorn");
engine.eval("print('test');"); // NPE potentiel!
// SOLUTION: Toujours verifier
ScriptEngine engine = manager.getEngineByName("nashorn");
if (engine == null) {
throw new RuntimeException("Moteur Nashorn non disponible. " +
"Utilisez Java 8-14 ou ajoutez GraalJS.");
}
2. Confusion entre Types Java et JavaScript
// PROBLEME: Les nombres JavaScript sont des Double
engine.eval("var nombre = 42;");
Integer nombre = (Integer) engine.get("nombre"); // ClassCastException!
// SOLUTION: Utiliser Number ou Double
Number nombre = (Number) engine.get("nombre");
int valeur = nombre.intValue(); // OK
3. Fuite de Memoire avec les Variables Globales
// PROBLEME: Les variables s'accumulent
for (int i = 0; i < 10000; i++) {
engine.put("temp_" + i, "data"); // Memoire non liberee!
}
// SOLUTION: Nettoyer apres usage
engine.put("tempData", "processing");
// ... traitement ...
engine.getBindings(ScriptContext.ENGINE_SCOPE).remove("tempData");
4. Problemes de Thread-Safety
// PROBLEME: ScriptEngine n'est PAS thread-safe par defaut
// Plusieurs threads utilisant le meme engine = comportement imprevisible
// SOLUTION: Un engine par thread ou synchronisation
private final ThreadLocal<ScriptEngine> engineHolder =
ThreadLocal.withInitial(() -> {
ScriptEngineManager manager = new ScriptEngineManager();
return manager.getEngineByName("nashorn");
});
public Object evaluateThreadSafe(String script) throws ScriptException {
return engineHolder.get().eval(script);
}
5. Ne Pas Gerer les Exceptions JavaScript
// PROBLEME: Exception JavaScript non capturee correctement
try {
engine.eval("throw new Error('Erreur volontaire');");
} catch (ScriptException e) {
// Le message peut etre peu informatif
System.err.println(e.getMessage());
}
// SOLUTION: Extraire plus d'informations
try {
engine.eval("throw new Error('Erreur volontaire');");
} catch (ScriptException e) {
Throwable cause = e.getCause();
if (cause != null) {
System.err.println("Cause: " + cause.getMessage());
}
System.err.println("Script: ligne " + e.getLineNumber());
}
Conclusion
L’integration de scripts JavaScript avec Java via Nashorn offre une flexibilite remarquable pour les applications necessitant du scripting dynamique. Dans cet article, nous avons couvert :
- Configuration : Comment initialiser correctement le moteur Nashorn
- Variables globales : Les methodes
put()etget()pour l’echange de donnees - Bindings : Le controle avance de la portee des variables
- Objets complexes : Le passage de Maps, Lists et objets personnalises
- Interfaces Java : L’implementation d’interfaces depuis JavaScript
- Bonnes pratiques : Gestion des erreurs, reutilisation, isolation
- Pieges courants : Thread-safety, types, fuites memoire
Alternatives Modernes
Pour les projets sur Java 15+, considerez ces alternatives :
| Alternative | Avantages |
|---|---|
| GraalJS | Compatible ECMAScript 2022, performances elevees |
| Rhino | Mature, fonctionne sur toutes versions Java |
| J2V8 | Binding V8, tres rapide |
Pour Aller Plus Loin
- GraalVM : graalvm.org - Runtime polyglotte moderne
- JSR 223 : Specification de l’API Scripting Java
- ECMAScript : Comprendre les differences entre ES5 (Nashorn) et ES6+
N’hesitez pas a partager vos experiences et questions dans les commentaires!
In-Article Ad
Dev Mode
Tags
Mahmoud DEVO
Senior Full-Stack Developer
I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.
Enjoyed this article?
Subscribe to get more tech content delivered to your inbox.
Related Articles
Manipuler les classes Java avec ASM et Javassist : bytecode, instrumentation et fichiers JAR
Apprenez a manipuler les classes Java avec ASM et Javassist : chargement, modification du bytecode, instrumentation et creation de fichiers JAR.
Synchronisation Java avec AtomicInteger : eviter la contention et optimiser les performances
Decouvrez comment utiliser les types atomiques Java (AtomicInteger, AtomicLong, AtomicReference, AtomicBoolean) pour reduire la contention, eviter les blocages et ameliorer les performances.
Les avantages des flux tampons pour une performance optimale
Ameliorez les performances de votre code Java avec les flux tampons ! Decouvrez comment reduire considerablement le nombre d'appels systeme, optimiser l'utilisation des types primitifs, gerer efficacement la journalisation et iterer sur les Maps de maniere performante.