Java-Skriptsprachen: Welche ist die richtige für Sie?

Die Anforderungen einiger Java-Anwendungen machen die Integration in eine Skriptsprache erforderlich. Beispielsweise müssen Ihre Benutzer möglicherweise Skripts schreiben, die die Anwendung steuern, erweitern oder Schleifen und andere Flusssteuerungskonstrukte enthalten. In solchen Fällen ist es sinnvoll, einen Skriptspracheninterpreter zu unterstützen, der Benutzerskripte lesen und dann für die Klassen Ihrer Java-Anwendung ausführen kann. Führen Sie zur Ausführung dieser Aufgabe einen Java-basierten Skriptspracheninterpreter in derselben JVM wie Ihre Anwendung aus.

Unterstützungsbibliotheken wie das Bean Scripting Framework von IBM oder die Bibliothek "Ramnivas Laddad", die in "Scripting Power spart den Tag für Ihre Java-Apps" ( JavaWorld, Oktober 1999) entwickelt wurde, helfen Ihnen dabei, verschiedene Skriptsprachen erfolgreich in Ihr Java-Programm zu integrieren. Solche Frameworks erfordern keine wesentlichen Änderungen an Ihrer Java-Anwendung und ermöglichen es Ihrem Java-Programm, Skripte auszuführen, die in Tcl, Python und anderen Sprachen geschrieben sind.

Die Skripte eines Benutzers können auch direkt auf die Klassen Ihrer Java-Anwendung verweisen, als wären diese Skripte ein weiterer Teil Ihres Programms. Das ist gut und schlecht. Es ist gut, wenn Sie möchten, dass Skripte Regressionstests für Ihr Programm durchführen und einfache Aufrufe vom Skript in Ihre Anwendung ausführen müssen. Es ist schlecht, wenn das Skript eines Benutzers gegen die Interna Ihres Programms anstatt gegen eine vereinbarte API arbeitet und somit die Integrität Ihres Programms gefährdet. Planen Sie daher, die API zu veröffentlichen, für die Ihre Benutzer Skripte schreiben sollen, und stellen Sie klar, dass der Rest des Programms nicht zulässig ist. Sie können auch die Klassennamen und -methoden verschleiern, für die Kunden keine Skripts schreiben sollen, aber die API-Klassen und Methodennamen in Ruhe lassen. Auf diese WeiseIch mache es weniger wahrscheinlich, dass ein abenteuerlustiger Benutzer gegen eine Klasse codiert, die Sie nicht wollten.

Es ist bemerkenswert, mehrere Skriptsprachen in Ihr Java-Programm einzubinden. Überlegen Sie jedoch zweimal, ob Sie eine kommerzielle Anwendung schreiben. Sie öffnen eine Dose Würmer, indem Sie versuchen, allen Benutzern alles zu bieten. Sie müssen das Problem der Konfigurationsverwaltung berücksichtigen, da zumindest einige der verschiedenen Skriptinterpreter regelmäßig aktualisiert und veröffentlicht werden. Sie müssen also sicherstellen, welche Version jedes Scripting-Interpreters mit welcher Version Ihrer Anwendung sinnvoll ist. Wenn ein Benutzer eine neuere Version eines dieser Interpreter in den Installationsbaum Ihrer Anwendung einfügt (in der Hoffnung, einen Fehler in der älteren Version zu beheben), führt er jetzt eine nicht getestete Konfiguration Ihrer Java-Anwendung aus. Tage oder Wochen später, wenn der Benutzer einen Fehler in Ihrer Anwendung findet und meldet, der von der neueren Scripting-Interpreter-Version entdeckt wurde,Sie werden die Änderung des Scripting-Interpreters wahrscheinlich nicht gegenüber Ihren Kundenbetreuern erwähnen, was es Ihren Ingenieuren erschwert, das Problem zu reproduzieren.

Darüber hinaus werden Kunden wahrscheinlich darauf bestehen, dass Sie eine Fehlerbehebung für den von Ihrer Anwendung unterstützten Skriptinterpreter anbieten. Einige Dolmetscher werden über ein Open-Source-Modell aktiv gewartet und aktualisiert. In diesen Fällen können Experten Ihnen helfen, das Problem zu umgehen, den Interpreter zu patchen oder eine Fehlerbehebung in einer zukünftigen Version zu erhalten. Dies ist wichtig, da Sie ohne Unterstützung möglicherweise die unangenehme Aufgabe haben, das Problem selbst zu beheben, und Skriptinterpreter zwischen 25.000 und 55.000 Codezeilen ausführen.

Um das Fix-it-yourself-Szenario zu vermeiden, können Sie jeden Skriptinterpreter, den Sie mit Ihrer Anwendung unterstützen möchten, gründlich testen. Stellen Sie für jeden Interpreter sicher, dass der Interpreter die gängigsten Verwendungsszenarien ordnungsgemäß handhabt, dass keine großen Speicherblöcke auslaufen, wenn Sie mit langen und anspruchsvollen Skripten auf den Interpreter hämmern, und dass nichts Unerwartetes passiert, wenn Sie Ihr Programm und Ihre Skriptdolmetscher einsetzen die Hände anspruchsvoller Betatester. Ja, solche Vorab-Tests kosten Zeit und Ressourcen. Trotzdem ist das Testen eine gute Zeit.

Die Lösung: Halten Sie es einfach

Wenn Sie Scripting in Ihrer Java-Anwendung unterstützen müssen, wählen Sie einen Scripting-Interpreter aus, der Ihren Anwendungsanforderungen und Ihrem Kundenstamm am besten entspricht. Auf diese Weise vereinfachen Sie den Interpreter-Integrationscode, senken die Kosten für den Kundensupport und verbessern die Konsistenz Ihrer Anwendung. Die schwierige Frage ist: Wenn Sie nur eine Skriptsprache standardisieren müssen, welche wählen Sie?

Ich habe mehrere Scripting-Interpreter verglichen, beginnend mit einer Liste von Sprachen, einschließlich Tcl, Python, Perl, JavaScript und BeanShell. Dann, ohne eine detaillierte Analyse durchzuführen, stieß ich Perl aus der Überlegung heraus. Warum? Weil in Java kein Perl-Interpreter geschrieben ist. Wenn der von Ihnen ausgewählte Skriptinterpreter wie Perl in nativem Code implementiert ist, ist die Interaktion zwischen Ihrer Anwendung und dem Skriptcode weniger direkt, und Sie müssen für jedes Betriebssystem, das Sie interessiert, mindestens eine native Binärdatei mit Ihrem Java-Programm liefern. Da sich viele Entwickler aufgrund der Portabilität der Sprache für Java entscheiden, bleibe ich diesem Vorteil treu, indem ich mich an einen Skriptinterpreter halte, der keine Abhängigkeit von nativen Binärdateien schafft. Java ist plattformübergreifend und ich möchte, dass mein Scripting-Interpreter es auch ist. Im Gegensatz,Java-basierte Interpreter gibt es für Tcl, Python, JavaScript und BeanShell, sodass sie im selben Prozess und in derselben JVM wie der Rest Ihrer Java-Anwendung ausgeführt werden können.

Basierend auf diesen Kriterien umfasst die Vergleichsliste für Skriptinterpreter:

  • Jacl: Die Tcl Java Implementierung
  • Jython: Die Python Java-Implementierung
  • Rhino: Die JavaScript-Java-Implementierung
  • BeanShell: Ein in Java geschriebener Java-Quellinterpreter

Nachdem wir die Liste der Skriptsinterpreter-Sprachen auf Tcl, Python, JavaScript und BeanShell heruntergefiltert haben, gelangen wir zu den ersten Vergleichskriterien.

Der erste Maßstab: Machbarkeit

Für den ersten Benchmark, Machbarkeit, habe ich die vier Dolmetscher untersucht, um festzustellen, ob sie durch irgendetwas nicht mehr verwendet werden können. Ich schrieb einfache Testprogramme in jeder Sprache, führte meine Testfälle gegen sie aus und stellte fest, dass jedes eine gute Leistung erbrachte. Alle arbeiteten zuverlässig oder erwiesen sich als einfach zu integrieren. Während jeder Dolmetscher ein würdiger Kandidat zu sein scheint, was würde einen Entwickler dazu bringen, einen anderen vorzuziehen?

  • Jacl: Wenn Sie möchten, dass Tk-Konstrukte in Ihren Skripten Benutzeroberflächenobjekte erstellen, sehen Sie sich das Swank-Projekt für Java-Klassen an, die Javas Swing-Widgets in Tk einschließen. Die Distribution enthält keinen Debugger für Jacl-Skripte.
  • Jython: Unterstützt Skripte, die in der Python-Syntax geschrieben wurden. Anstatt wie in vielen Sprachen geschweifte Klammern oder Anfang-Ende-Markierungen zu verwenden, um den Kontrollfluss anzuzeigen, verwendet Python Einrückungsstufen, um anzuzeigen, welche Codeblöcke zusammengehören. Ist das ein Problem? Es hängt von Ihnen und Ihren Kunden ab und ob es Ihnen etwas ausmacht. Die Distribution enthält keinen Debugger für Jython-Skripte.
  • Rhino: Viele Programmierer verknüpfen JavaScript mit der Webseitenprogrammierung, aber diese JavaScript-Version muss nicht in einem Webbrowser ausgeführt werden. Ich habe keine Probleme bei der Arbeit damit gefunden. Die Distribution wird mit einem einfachen, aber nützlichen Skript-Debugger geliefert.
  • BeanShell: Java-Programmierer werden sich mit dem Verhalten dieses Quellinterpreten sofort zu Hause fühlen. Die Dokumentation von BeanShell ist gut gemacht, aber suchen Sie in Ihrem Buchladen nicht nach einem Buch über BeanShell-Programmierung - es gibt kein Buch. Auch das Entwicklungsteam von BeanShell ist sehr klein. Dies ist jedoch nur dann ein Problem, wenn die Auftraggeber zu anderen Interessen übergehen und andere nicht eingreifen, um ihre Schuhe zu füllen. Die Distribution enthält keinen Debugger für BeanShell-Skripte.

Der zweite Maßstab: Leistung

Für den zweiten Benchmark, die Leistung, habe ich untersucht, wie schnell die Scripting-Interpreter einfache Programme ausgeführt haben. Ich habe die Dolmetscher nicht gebeten, große Arrays zu sortieren oder komplexe Berechnungen durchzuführen. Stattdessen hielt ich mich an grundlegende allgemeine Aufgaben wie das Schleifen, das Vergleichen von Ganzzahlen mit anderen Ganzzahlen sowie das Zuweisen und Initialisieren großer ein- und zweidimensionaler Arrays. Einfacher geht es nicht, und diese Aufgaben sind häufig genug, dass die meisten kommerziellen Anwendungen sie zu der einen oder anderen Zeit ausführen. Ich habe auch überprüft, wie viel Speicher jeder Interpreter für die Instanziierung benötigt, und ein winziges Skript ausgeführt.

Aus Gründen der Konsistenz habe ich jeden Test in jeder Skriptsprache so ähnlich wie möglich codiert. Ich habe die Tests auf einem Toshiba Tecra 8100-Laptop mit einem 700-MHz-Pentium III-Prozessor und 256 MB RAM durchgeführt. Beim Aufrufen der JVM habe ich die Standardgröße des Heapspeichers verwendet.

Um eine Perspektive dafür zu bieten, wie schnell oder langsam diese Zahlen sind, habe ich die Testfälle auch in Java codiert und mit Java 1.3.1 ausgeführt. Ich habe auch die Tcl-Skripte, die ich für den Jacl-Skriptinterpreter geschrieben habe, in einem nativen Tcl-Interpreter erneut analysiert. Folglich können Sie in den folgenden Tabellen sehen, wie sich die Interpreter gegen native Interpreter behaupten.

Tabelle 1. Für die Schleifenzählung von 1 bis 1.000.000
Scripting-Interpreter Zeit
Java 10 Millisekunden
Tcl 1,4 Sekunden
Jacl 140 Sekunden
Jython 1,2 Sekunden
Nashorn 5 Sekunden
BeanShell 80 Sekunden
Tabelle 2. Vergleichen Sie 1.000.000 Ganzzahlen auf Gleichheit
Scripting-Interpreter Zeit
Java 10 Millisekunden
Tcl 2 Sekunden
Jacl 300 Sekunden
Jython 4 Sekunden
Nashorn 8 Sekunden
BeanShell 80 Sekunden
Tabelle 3. Ordnen Sie ein Array mit 100.000 Elementen zu und initialisieren Sie es
Scripting-Interpreter Zeit
Java 10 Millisekunden
Tcl .5 Sekunden
Jacl 25 Sekunden
Jython 1 Sekunde
Nashorn 1,3 Sekunden
BeanShell 22 Sekunden
Tabelle 4. Zuweisen und Initialisieren eines 500 x 500-Elementarrays
Scripting-Interpreter Zeit
Java 20 Millisekunden
Tcl 2 Sekunden
Jacl 45 Sekunden
Jython 1 Sekunde
Nashorn 7 Sekunden
BeanShell 18 Sekunden
Tabelle 5. Erforderlicher Speicher zum Initialisieren des Interpreters in der JVM
Scripting-Interpreter Speichergröße
Jacl Über 1 MB
Jython Über 2 MB
Nashorn Über 1 MB
BeanShell Über 1 MB

Was die Zahlen bedeuten

Jython ist mit beachtlichem Vorsprung der Schnellste auf den Benchmarks, Rhino liegt auf dem zweiten Platz. BeanShell ist langsamer, wobei Jacl das Heck bildet.

Whether these performance numbers matter to you depends on the tasks you want to do with your scripting language. If you have many hundreds of thousands of iterations to perform in your scripting functions, then Jacl or BeanShell might prove intolerable. If your scripts run few repetitive functions, then the relative differences in speeds between these interpreters seem less important.

It's worth mentioning that Jython doesn't seem to have built-in direct support for declaring two-dimensional arrays, but this can be worked around by using an array-of-arrays structure.

Although it was not a performance benchmark, it did take me more time to write the scripts in Jython than for the others. No doubt my unfamiliarity with Python caused some of the trouble. If you are a proficient Java programmer but are unfamiliar with Python or Tcl, you may find it easier to get going writing scripts with JavaScript or BeanShell than you will with Jython or Jacl, since there is less new ground to cover.

The third benchmark: Integration difficulty

The integration benchmark covers two tasks. The first shows how much code instantiates the scripting language interpreter. The second task writes a script that instantiates a Java JFrame, populates it with a JTree, and sizes and displays the JFrame. Although simple, these tasks prove valuable because they measure the effort to start using the interpreter, and also how a script written for the interpreter looks when it calls Java class code.

Jacl

To integrate Jacl into your Java application, you add the Jacl jar file to your classpath at invocation, then instantiate the Jacl interpreter prior to executing a script. Here's the code to create a Jacl interpreter:

import tcl.lang.*; public class SimpleEmbedded { public static void main(String args[]) { try { Interp interp = new Interp(); } catch (Exception e) { } } 

The Jacl script to create a JTree, put it in a JFrame, and size and show the JFrame, looks like this:

package require java set env(TCL_CLASSPATH) set mid [java::new javax.swing.JTree] set f [java::new javax.swing.JFrame] $f setSize 200 200 set layout [java::new java.awt.BorderLayout] $f setLayout $layout $f add $mid $f show 

Jython

To integrate Jython with your Java application, add the Jython jar file to your classpath at invocation, then instantiate the interpreter prior to executing a script. The code that gets you this far is straightforward:

import org.python.util.PythonInterpreter; import org.python.core.*; public class SimpleEmbedded { public static void main(String []args) throws PyException { PythonInterpreter interp = new PythonInterpreter(); } } 

The Jython script to create a JTree, put it in a JFrame, and show the JFrame is shown below. I avoided sizing the frame this time:

from pawt import swing import java, sys frame = swing.JFrame('Jython example', visible=1) tree = swing.JTree() frame.contentPane.add(tree) frame.pack() 

Rhino

As with the other interpreters, you add the Rhino jar file to your classpath at invocation, then instantiate the interpreter prior to executing a script:

import org.mozilla.javascript.*; import org.mozilla.javascript.tools.ToolErrorReporter; public class SimpleEmbedded { public static void main(String args[]) { Context cx = Context.enter(); } } 

The Rhino script to create a JTree, put it in a JFrame, and size and show the JFrame proves simple:

importPackage (java.awt); importPackage (Packages.javax.swing); frame = neuer Frame ("JavaScript"); frame.setSize (neue Dimension (200.200)); frame.setLayout (neues BorderLayout ()); t = neuer JTree (); frame.add (t, BorderLayout.CENTER); frame.pack (); frame.show ();