
Hallo Habr! Mein Name ist Dima, ich bin Entwickler im Team „Architektur“ bei hh.ru. Unter anderem mache ich es Kollegen leichter. Das Ausführen von Code in der Produktion ist eine typische Aufgabe. Als ich hörte, dass es Probleme damit gibt, beschloss ich, sie zu beheben.
Nicht immer können Datenänderungen einfach vorgenommen werden. UPDATE / INSERT - manchmal müssen Sie Validierung, Ereignisbusse und mehr verwenden. In solchen Fällen besteht die optimalste Lösung darin, beliebigen Code direkt in der Anwendung auszuführen. Wir haben Java. Als
JEP-222 erschien , dachte ich sofort, dass JShell das Scripting wieder bequemer machen könnte. Ein Wunder ist nicht geschehen, und daher finden Sie unter dem Schnitt einen nicht sehr tiefen Vergleich der berühmtesten Java-Interpreter (und "Near-Java") für JVM mit Beispielen. Ich lade alle Interessierten unter Katze ein.
Wir verwenden
BeanShell , um Skripte auszuführen, und für 2019 ist es schrecklich: Die letzte Version von
2016 , die mangelnde Unterstützung für Lambdas und sogar Generika - all dies zwingt uns, Code zu schreiben, den seit Java
1.4 niemand mehr geschrieben hat.
Kriterien
Bevor wir mit dem Vergleich beginnen, formulieren wir die Anforderungen für die integrierte Scripting-Engine. Nachdem ich mir am Kopf gekratzt hatte, stellte ich folgende Liste zusammen:
- Unterstützung für die aktuelle Java-Syntax;
- die Fähigkeit, einen externen Kontext an den Dolmetscher weiterzugeben;
- Fähigkeit, die Ausführung zu unterbrechen;
- Fähigkeit, E / A umzuleiten;
- informatives Feedback.
Je mehr die Sprache, in der wir Skripte schreiben, derjenigen ähnelt, die wir entwickeln, desto weniger Fehler - Hände erinnern sich. Wenn wir jedoch Fehler machen, die bei der Kompilierung festgestellt wurden, sollten sie es dem Entwickler ermöglichen, diese zu beheben - dies sind Hinweise auf die Namen der fehlenden Variablen, Zeilen, Stacktracks usw.
Als Nächstes sollten die Skripte in einem bestimmten Kontext mit Zugriff auf den Spring-Kontext für den Logger funktionieren, der die Skripte bereitstellt. Ohne eine solche Möglichkeit, den Kontext zu übertragen, wird sein Empfang zu einer Quest.
Wenn der Fehler dennoch zur Laufzeit durchgesickert ist, ist es eine schlechte Idee, die gesamte Instanz neu zu starten, um die Ausführung zu stoppen. Sie müssen also jederzeit in der Lage sein, die Ausführung des Skripts einfach zu unterbrechen.
Und die letzte - alle Nachrichten an die Systemausgabe während der Skriptarbeit sind nur im Kontext dieses Skripts sinnvoll. In Systemprotokollen ist eine solche Schlussfolgerung von geringem Nutzen. Daher möchte ich in der Lage sein, diese Nachrichten als Antwort umzuleiten.
Also lass uns gehen
- Unterstützung für die aktuelle Java-Syntax - ja
- die Fähigkeit, Kontext zu vermitteln - nein
- Fähigkeit, die Ausführung zu unterbrechen - ja
- die Fähigkeit, E / A umzuleiten - nein
- informatives Feedback - ja
Ich
muss sofort sagen
, dass
JEP-222 nicht darauf
abzielt , einen eingebetteten Interpreter zu erstellen - sein Ziel ist
REPL ,
dh die Fähigkeit, Code schnell zu prototypisieren. Dies ist mit einer Reihe von Konsequenzen verbunden.
Erstens hat life den Java-Compiler nicht darauf vorbereitet, dass Sie eine Methode außerhalb der Klasse deklarieren und Variablen verwenden können, die noch nicht im Hauptteil der Methode deklariert sind. Daher ist die Ausführung selbst hinter einer beeindruckenden Abstraktionsebene verborgen.
Zweitens kann REPL möglicherweise nicht lokal, sondern irgendwo auf einem Remotecomputer ausgeführt werden, sodass die API unter Berücksichtigung dieser Funktionen erstellt wird. Ich denke, dies ist der Hauptgrund, warum die API nicht in der Lage ist, einen externen Kontext an den Interpreter zu übergeben und E / A umzuleiten.
Darüber hinaus gibt es verschiedene Startmodi:
Remote , wenn die Shell über JDI eine Verbindung zum Computer herstellt, und
Local . Da es keine Möglichkeit gibt, den Kontext programmgesteuert zu vermitteln, wir dies aber dennoch wirklich wollen, bleibt die Hoffnung nur im lokalen Modus und dass wir die
Codegenerierung verwenden könnenLeider wurde der lokale Modus eindeutig nicht als Hauptmodus konzipiert -
diese Art von Skript ruft den
Deadlock des Compilers auf. Trotz der Tatsache, dass der gleiche Code im JDI-Modus ohne Probleme funktioniert.
Daher musste ich auf die Verwendung von JShell verzichten, obwohl die API im Allgemeinen seltsam, aber verständlich ist - wir geben das Skript an die Eingabe weiter, wir erhalten den Ereignisstrom, für jeden von ihnen können wir den Status überprüfen, Fehler abrufen und Rechnungsinformationen abrufen. Durch Fehler können wir den Ausdruck identifizieren, in dem er zulässig war:
UPDATE: Gesammelte ScriptEngine von JShell . Dafür musste ich aber meinen
Startmodus schreiben
- Unterstützung für die aktuelle Java-Syntax - Nr
- die Fähigkeit, Kontext zu vermitteln - ja
- Fähigkeit, die Ausführung zu unterbrechen - ja
- die Fähigkeit, E / A umzuleiten - ja (erfordert jedoch die Verwendung spezieller Methoden)
- informatives Feedback - ja
Durch das Scheitern haben wir darauf geachtet, was wir jetzt verwenden. Nach einer langen Pause schien das Projekt zum Leben zu erwecken, und nach der
Roadmap geht es zuversichtlich in Richtung einer Veröffentlichung, die alle unsere Probleme lösen wird - viele Funktionen sollten jetzt funktionieren.
Zum Zeitpunkt dieses Schreibens unterstützte Beanshell zwar Generika, aber Lambdas funktionieren immer noch nicht. Vielleicht wird sich die Situation durch die Freigabe der Situation ändern.
Aber in Bezug auf die Integration ist die Engine recht freundlich - Unterstützung für Standard-javax.scripting, Laufzeitfehler sind ausführlich genug:

Die Verwendung von Lambda-freien Streams ist jedoch die Hölle, die in der Hölle brennt. Es könnte sogar einfacher sein, in einer anderen Sprache zu schreiben. Also habe ich beschlossen, mir das Segment "Near Java" genauer anzusehen. Und hier natürlich der erste Kandidat für die Rolle eines Skriptinterpreten
- Unterstützung für die aktuelle Java-Syntax - Nr
- die Fähigkeit, Kontext zu vermitteln - nein
- Fähigkeit, die Ausführung zu unterbrechen - ja
- die Fähigkeit, E / A umzuleiten - nein
- informatives Feedback - ja
Java-Code wird mit viel Glück ein gültiger Kotlin-Code sein. Aber es ist mir nicht gelungen, etwas zumindest irgendwie angemessenes für Java in Kotlin zu starten, aber versuchen wir es trotzdem.
Kotlin hat bereits seit
einigen Jahren die Unterstützung von javax.scripting angekündigt .
Das erste Problem, mit dem wir uns befassen müssen, ist die Abhängigkeitshölle.
Kotlin-Compiler enthält die org.jdom-Klassen, die mit org.jdom zu kämpfen begonnen haben, in der Anwendung und schließt sie ab. Wir haben also kotlin-compiler-embeddeddable, in dem alle diese Klassen in benutzerdefinierten Paketen abgelegt werden.
Nach der Konfiguration stellt sich jedoch heraus, dass die Übertragung des externen Kontexts nicht funktioniert. Und jetzt ist dies ein ernstes Problem, bis zu seiner Lösung macht es keinen Sinn, tiefer zu graben. Wenn Sie wissen, was das Problem ist und wie es behoben werden kann, schreiben Sie in die Kommentare.
Fehler sind auch ziemlich ausführlich:

- Unterstützung für die aktuelle Java-Syntax - nein, aber es gibt Analoga
- die Fähigkeit, Kontext zu vermitteln - ja
- Fähigkeit, die Ausführung zu unterbrechen - ja
- die Fähigkeit, E / A umzuleiten - ja
- informatives Feedback - ja
Grooves unterstützt nicht nur javax.scripting, sondern bietet auch eine eigene, erweiterte API für die Interpreter-Integration. Beispielsweise ist es möglich, eine AST-Transformation zu übergeben, mit der Sie
nach jedem Ausdruck einen
bedingten Interrupt hinzufügen
können . Das Ding ist so mächtig, dass es schon
beängstigend ist .
Darüber hinaus kann Java-Code (und insbesondere Beanshell-Code) durchaus gültiger Groove-Code sein.
Die Integration und der Testbetrieb waren erfolgreich, mit Ausnahme der Blattinitialisierung und der Lambda-Syntax (sie müssen in geschweiften Klammern eingeschlossen werden) funktionierten die vorhandenen Binshell-Skripte problemlos. Fehler sind mehr als ausführlich:

Vielleicht ist es heute der einzige Dolmetscher, der es Ihnen einerseits ermöglicht, Code für das Beispiel im Jahr 2019 zu schreiben, und andererseits alle Anforderungen erfüllt, die es angemessen sind, dem Dolmetscher vorzulegen.
Welche Schlussfolgerungen können wir ziehen?
In erster Linie: REPL ist keine Scripting-Engine. Es mag wie ein Schritt von der Befehlszeile zur Integration in die Anwendung erscheinen, aber bei näherer Betrachtung stellt sich heraus, dass diese Tools unterschiedliche Aufgaben haben und sich manchmal widersprechen.
Das zweite - heute gibt es kein einziges Tool zum Ausführen von Java-Skripten. Wenn Sie also ein solches Tool benötigen, sollten Sie sich darauf einstellen, eine neue Syntax zu erlernen.