Introduction à Nashorn

Introduction à Nashorn

1. introduction

Cet article se concentre surNashorn –, le nouveau moteur JavaScript par défaut pour la JVM à partir de Java 8.

De nombreuses techniques sophistiquées ont été utilisées pour rendre les ordres de grandeur deNashorn plus performants que son prédécesseur appeléRhino,, c'est donc un changement intéressant.

Voyons quelques-unes des façons dont il peut être utilisé.

2. Ligne de commande

JDK 1.8 inclut un interpréteur de ligne de commande appeléjjs qui peut être utilisé pour exécuter des fichiers JavaScript ou, s'il est démarré sans argument, comme REPL (shell interactif):

$ $JAVA_HOME/bin/jjs hello.js
Hello World

Ici le fichierhello.js contient une seule instruction:print(“Hello World”);

Le même code peut être exécuté de manière interactive:

$ $JAVA_HOME/bin/jjs
jjs> print("Hello World")
Hello World

Vous pouvez également demander au runtime * nix d'utiliserjjs pour exécuter un script cible en ajoutant un#!$JAVA_HOME/bin/jjs comme première ligne:

#!$JAVA_HOME/bin/jjs
var greeting = "Hello World";
print(greeting);

Et puis le fichier peut être exécuté comme d'habitude:

$ ./hello.js
Hello World

3. Moteur de script intégré

Le deuxième moyen et probablement le plus courant d'exécuter JavaScript à partir de la JVM consiste à utiliser leScriptEngine. JSR-223 définit un ensemble d'API de script, permettant une architecture de moteur de script enfichable pouvant être a une implémentation JVM, bien sûr).

Créons un moteur JavaScript:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");

Object result = engine.eval(
   "var greeting='hello world';" +
   "print(greeting);" +
   "greeting");

Ici, nous créons un nouveauScriptEngineManager et lui demandons immédiatement de nous donner unScriptEngine nomménashorn. Ensuite, nous passons quelques instructions et obtenons le résultat qui, de manière prévisible, s'avère être unString "hello world".

4. Passer des données au script

Les données peuvent être passées dans le moteur en définissant un objetBindings et en le passant comme second paramètre à la fonctioneval:

Bindings bindings = engine.createBindings();
bindings.put("count", 3);
bindings.put("name", "example");

String script = "var greeting='Hello ';" +
  "for(var i=count;i>0;i--) { " +
  "greeting+=name + ' '" +
  "}" +
  "greeting";

Object bindingsResult = engine.eval(script, bindings);

L'exécution de cet extrait de code produit: «Hello example example example».

5. Appel de fonctions JavaScript

Il est bien entendu possible d’appeler des fonctions JavaScript à partir de votre code Java:

engine.eval("function composeGreeting(name) {" +
  "return 'Hello ' + name" +
  "}");
Invocable invocable = (Invocable) engine;

Object funcResult = invocable.invokeFunction("composeGreeting", "example");

Cela renverra «Hello example».

6. Utilisation d'objets Java

Puisque nous exécutons dans la machine virtuelle Java, il est possible d’utiliser des objets Java natifs à partir de code JavaScript.

Ceci est accompli en utilisant un objetJava:

Object map = engine.eval("var HashMap = Java.type('java.util.HashMap');" +
  "var map = new HashMap();" +
  "map.put('hello', 'world');" +
  "map");

7. Extensions linguistiques

Nashorn is targeting ECMAScript 5.1 mais il fournit des extensions pour rendre l'utilisation de JavaScript un peu plus agréable.

7.1. Itération des collections avec For-Each

For-each est une extension pratique pour faciliter l'itération sur diverses collections:

String script = "var list = [1, 2, 3, 4, 5];" +
  "var result = '';" +
  "for each (var i in list) {" +
  "result+=i+'-';" +
  "};" +
  "print(result);";

engine.eval(script);

Ici, nous joignons les éléments d'un tableau en utilisant la construction d'itérationfor-each.

La sortie résultante sera1-2-3-4-5-.

7.2. Littéraux de fonction

Dans les déclarations de fonction simples, vous pouvez omettre les accolades:

function increment(in) ++in

Évidemment, cela ne peut être fait que pour des fonctions simples et à une ligne.

7.3. Clauses de capture conditionnelle

Il est possible d'ajouter des clauses catch protégées qui ne sont exécutées que si la condition spécifiée est vraie:

try {
    throw "BOOM";
} catch(e if typeof e === 'string') {
    print("String thrown: " + e);
} catch(e) {
    print("this shouldn't happen!");
}

Cela affichera «String thrown: BOOM».

7.4. Tableaux typés et conversions de types

Il est possible d'utiliser des tableaux typés Java et de convertir des tableaux en JavaScript:

function arrays(arr) {
    var javaIntArray = Java.to(arr, "int[]");
    print(javaIntArray[0]);
    print(javaIntArray[1]);
    print(javaIntArray[2]);
}

Nashorn effectue ici des conversions de type pour s'assurer que toutes les valeurs du tableau JavaScript typé dynamiquement peuvent tenir dans les tableaux Java entiers uniquement.

Le résultat de l'appel de la fonction ci-dessus avec l'argument[100, “1654”, true] entraîne la sortie de 100, 1654 et 1 (tous les nombres).

Les valeursString et booléennes ont été implicitement converties en leurs équivalents entiers logiques.

7.5. Définition du prototype d’objet avecObject.setPrototypeOf

Nashorn définit une extension API qui nous permet de changer le prototype d'un objet:

Object.setPrototypeOf(obj, newProto)

Cette fonction est généralement considérée comme une meilleure alternative àObject.prototype.proto, elle devrait donc être la meilleure façon de définir le prototype d’objet dans tout nouveau code.

7.6. MagiquesnoSuchProperty etnoSuchMethod

Il est possible de définir des méthodes sur un objet qui seront appelées à chaque fois qu’une propriétéundefined est accédée ou qu’une méthodeundefined est appelée:

var demo = {
    __noSuchProperty__: function (propName) {
        print("Accessed non-existing property: " + propName);
    },

    __noSuchMethod__: function (methodName) {
        print("Invoked non-existing method: " + methodName);
    }
};

demo.doesNotExist;
demo.callNonExistingMethod()

Cela va imprimer:

Accessed non-existing property: doesNotExist
Invoked non-existing method: callNonExistingMethod

7.7. Lier les propriétés d'objet avecObject.bindProperties

Object.bindProperties peut être utilisé pour lier des propriétés d'un objet à un autre:

var first = {
    name: "Whiskey",
    age: 5
};

var second = {
    volume: 100
};

Object.bindProperties(first, second);

print(first.volume);

second.volume = 1000;
print(first.volume);

Notez que cela crée une liaison «en direct» et que toute mise à jour de l'objet source est également visible via la cible de la liaison.

7.8. Emplacements

Le nom du fichier actuel, le répertoire et une ligne peuvent être obtenus à partir des variables globalesFILE, DIR, LINE:

print(__FILE__, __LINE__, __DIR__)

7.9. Extensions à String.prototype

Il existe deux extensions simples mais très utiles queNashorn fournit sur le prototype deString. Ce sont les fonctionstrimRight ettrimLeft qui, sans surprise, renvoient une copie desString avec les espaces supprimés:

print("   hello world".trimLeft());
print("hello world     ".trimRight());

Imprimera «bonjour monde» deux fois sans espaces de début ni de fin.

7.10. FonctionJava.asJSONCompatible

En utilisant cette fonction, nous pouvons obtenir un objet compatible avec les attentes des bibliothèques JSON Java.

À savoir, que si lui-même, ou tout objet accessible de manière transitoire à travers lui, est un tableau JavaScript, alors ces objets seront exposés en tant queJSObject qui implémentent également l'interfaceList pour exposer les éléments du tableau.

Object obj = engine.eval("Java.asJSONCompatible(
  { number: 42, greet: 'hello', primes: [2,3,5,7,11,13] })");
Map map = (Map)obj;

System.out.println(map.get("greet"));
System.out.println(map.get("primes"));
System.out.println(List.class.isAssignableFrom(map.get("primes").getClass()));

Cela affichera «hello» suivi de[2, 3, 5, 7, 11, 13] suivi detrue.

8. Chargement de scripts

Il est également possible de charger un autre fichier JavaScript à partir desScriptEngine:

load('classpath:script.js')

Un script peut également être chargé à partir d'une URL:

load('/script.js')

Gardez à l'esprit que JavaScript n'a pas de concept d'espaces de noms, de sorte que tout s'empile dans la portée globale. Cela permet aux scripts chargés de créer des conflits de noms avec votre code ou entre eux. Cela peut être atténué en utilisant la fonctionloadWithNewGlobal:

var math = loadWithNewGlobal('classpath:math_module.js')
math.increment(5);

Avec lesmath_module.js suivants:

var math = {
    increment: function(num) {
        return ++num;
    }
};

math;bai

Ici, nous définissons un objet nommémath qui a une seule fonction appeléeincrement. En utilisant ce paradigme, nous pouvons même émuler la modularité de base!

8. Conclusion

Cet article a exploré certaines fonctionnalités du moteur savaScriptNashorn J. Les exemples présentés ici utilisaient des scripts littéraux de chaîne, mais pour des scénarios réels, vous souhaiterez probablement conserver votre script dans des fichiers séparés et les charger à l'aide d'une classeReader.

Comme toujours, le code de cet article est entièrement disponibleover on Github.