Java REPL vous n'avez pas ScriptEngine



Bonjour, Habr! Je m'appelle Dima, je suis développeur dans l'équipe «Architecture» de hh.ru. Entre autres choses, je facilite les choses pour mes collègues. L'exécution de code en production est une tâche typique. Par conséquent, quand j'ai entendu qu'il y avait des problèmes avec cela, j'ai décidé de les résoudre.

Les modifications de données ne peuvent pas toujours être simplifiées. MISE À JOUR / INSÉRER - vous devez parfois utiliser la validation, des bus d'événements, etc. Dans de tels cas, la solution la plus optimale consiste à exécuter du code arbitraire directement dans l'application. Nous avons Java, donc quand JEP-222 est apparu , j'ai immédiatement pensé que JShell pourrait à nouveau rendre le script plus pratique. Un miracle ne s'est pas produit, et donc sous la coupe, vous trouverez une comparaison pas très approfondie des interprètes Java les plus célèbres (et «quasi-Java») pour jvm avec des exemples. J'invite toutes les personnes intéressées par cat.

Pour exécuter les scripts, nous utilisons BeanShell , et pour 2019 c'est terrible: la dernière version de 2016 , le manque de support pour les lambdas et même les génériques - tout cela nous oblige à écrire du code que personne n'a écrit depuis Java 1.4 .

Critères



Avant de commencer la comparaison, nous formulons les exigences pour le moteur de script intégré. Après m'être gratté la tête, j'ai fait la liste suivante:

  1. prise en charge de la syntaxe java actuelle;
  2. la capacité de transmettre un contexte externe à l'interprète;
  3. capacité d'interrompre l'exécution;
  4. possibilité de rediriger les E / S;
  5. rétroaction informative.

Plus la langue dans laquelle nous écrivons les scripts ressemble à celle que nous développons, moins il y a d'erreurs - les mains s'en souviennent. Mais lorsque nous faisons des erreurs qui ont été identifiées lors de la compilation, elles doivent permettre au développeur de les corriger - ce sont des indications sur les noms des variables manquantes, des lignes, des stacktracks, etc.
Ensuite, les scripts doivent fonctionner dans un contexte spécifique, avec accès au contexte Spring, à l'enregistreur qui servira les scripts. Sans une telle opportunité de transférer le contexte, sa réception se transforme en quête.
Si l'erreur s'est néanmoins infiltrée dans le runtime, redémarrer toute l'instance pour arrêter l'exécution est une mauvaise idée, il vous suffit donc de pouvoir simplement interrompre l'exécution du script à tout moment.
Et le dernier - tous les messages à la sortie du système pendant le travail de script n'ont de sens que dans le contexte de ce script. Dans les journaux système, une telle conclusion est peu utile. Par conséquent, je veux pouvoir rediriger ces messages en réponse.

Alors allons-y

Jshell



  • prise en charge de la syntaxe java actuelle - oui
  • la capacité de transmettre le contexte - non
  • possibilité d'interrompre l'exécution - oui
  • la possibilité de rediriger les E / S - non
  • rétroaction informative - oui

Je dois dire tout de suite que JEP-222 n'a pas pour objectif de créer un interpréteur intégré - son objectif est REPL , c'est-à-dire la capacité de prototyper rapidement du code. Ceci est lourd de conséquences.

Tout d'abord, life n'a pas préparé le compilateur Java au fait que vous pouvez déclarer une méthode en dehors de la classe et utiliser des variables qui ne sont pas encore déclarées dans le corps de la méthode. Par conséquent, l'exécution elle-même est cachée derrière une impressionnante couche d'abstraction.

Deuxièmement, REPL peut très bien être exécuté non pas localement, mais quelque part sur une machine distante, donc l'API est conçue avec de telles fonctionnalités à l'esprit. Je pense que c'est la principale raison pour laquelle l'API n'a pas la capacité de passer un contexte externe à l'interpréteur et de rediriger les E / S.
De plus, il existe différents modes de lancement - à distance , lorsque le shell se connecte à la machine via JDI, et local . Puisqu'il n'y a aucune possibilité de transmettre le contexte par programme, mais nous le voulons toujours, l'espoir ne reste que dans le mode local et que nous pouvons utiliser la génération de code
Mais, malheureusement, le mode local n'a clairement pas été conçu comme le principal - ce type de script appelle le blocage sur le compilateur. Malgré le fait que le même code en mode JDI fonctionne sans problème.

J'ai donc dû abandonner l'utilisation de JShell, bien qu'en général l'API soit étrange, mais compréhensible - nous donnons le script à l'entrée, nous obtenons le flux d'événements, pour chacun d'eux nous pouvons vérifier le statut, obtenir des erreurs et des informations de facturation. Les erreurs nous permettent d'identifier l'expression dans laquelle elle a été autorisée:



MISE À JOUR: Rassemblé ScriptEngine de JShell . Mais pour cela j'ai dû écrire mon mode de lancement

Beanshell



  • prise en charge de la syntaxe java actuelle - non
  • la capacité de transmettre le contexte - oui
  • possibilité d'interrompre l'exécution - oui
  • la possibilité de rediriger les E / S - oui (mais nécessite l'utilisation de méthodes spéciales)
  • rétroaction informative - oui

L'échec nous a fait prêter attention à ce que nous utilisons actuellement. Après une longue pause, le projet a semblé prendre vie, et à en juger par la feuille de route , il se dirige en toute confiance vers une version qui résoudra tous nos problèmes - de nombreuses fonctionnalités devraient fonctionner maintenant.

Au moment d'écrire ces lignes, le beanshell supportait en effet les génériques, mais les lambdas ne fonctionnent toujours pas. Peut-être qu'à la sortie de la situation, la situation changera.
Mais en termes d'intégration, le moteur est assez convivial - prise en charge de javax.scripting standard, les erreurs d'exécution sont suffisamment verbeuses:



Cependant, l'utilisation de flux sans lambda est un enfer qui brûle en enfer. Il pourrait même être plus facile d'écrire dans une autre langue. J'ai donc décidé de regarder de plus près le segment "near-java". Et le premier candidat pour le rôle d'un interprète de script ici, bien sûr

Kotlin


  • prise en charge de la syntaxe java actuelle - non
  • la capacité de transmettre le contexte - non
  • possibilité d'interrompre l'exécution - oui
  • la possibilité de rediriger les E / S - non
  • rétroaction informative - oui

Le code Java, avec beaucoup de chance, sera un code kotlin valide. Mais je n'ai pas réussi à lancer quoi que ce soit au moins de manière adéquate sur java dans kotlin, mais essayons néanmoins.
Kotlin a déjà annoncé la prise en charge de javax.scripting depuis quelques années.

Le premier problème auquel nous devons faire face est l'enfer de la dépendance.
Kotlin-compiler inclut les classes org.jdom qui ont commencé à se battre avec org.jdom dans l'application et à envelopper ... Donc, nous avons kotlin-compiler-embeddable, où toutes ces classes sont placées dans des packages personnalisés.

Cependant, après configuration, il s'avère que le transfert du contexte externe ne fonctionne pas. Et maintenant, c'est un problème grave, jusqu'à sa solution, il est inutile de creuser plus profondément. Si vous savez quel est le problème et comment le résoudre, écrivez dans les commentaires.

Les erreurs sont également assez verbeuses:



Groovy



  • prise en charge de la syntaxe java actuelle - non, mais il existe des analogues
  • la capacité de transmettre le contexte - oui
  • possibilité d'interrompre l'exécution - oui
  • la possibilité de rediriger les E / S - oui
  • rétroaction informative - oui

Grooves, en plus de prendre en charge javax.scripting, fournit sa propre API plus avancée pour l'intégration de l'interpréteur. Par exemple, il est possible de passer une transformation AST qui vous permet d'ajouter une interruption conditionnelle après chaque expression . La chose est si puissante qu'elle fait déjà peur .

De plus, le code Java (et surtout le beanshell) peut être un code groove tout à fait valide.
L'intégration et le test ont réussi, à l'exception de l'initialisation des feuilles et de la syntaxe lambda (ils doivent être entourés d'accolades), les scripts binshell existants ont fonctionné sans problème. Les erreurs sont plus que verbeuses:



C'est peut-être aujourd'hui le seul interprète qui, d'une part, vous permet d'écrire du code pour l'exemple en 2019, et d'autre part, il répond à toutes les exigences qu'il est raisonnable de présenter à l'interprète.

Quelles conclusions pouvons-nous tirer?


D'abord et avant tout: REPL n'est pas un moteur de script. Cela peut sembler être une étape de la ligne de commande à l'intégration dans l'application, mais en y regardant de plus près, il s'avère que ces outils ont des tâches différentes, parfois contradictoires.

La seconde - aujourd'hui, il n'y a pas un seul outil pour exécuter des scripts en Java, donc si vous avez besoin d'un tel outil, soyez prêt à apprendre une nouvelle syntaxe.

Source: https://habr.com/ru/post/fr461027/


All Articles