JAXenter.de

Das Portal für Java, Architektur, Cloud & Agile

Groovy für Java-Entwickler: Dynamische Programmierung auf der Java-Plattform

Dynamischer Nachwuchs

Groovy für Java-Entwickler: Dynamische Programmierung auf der Java-Plattform

Dierk König

Java bezeichnet zwei sehr verschiedene Dinge: die Programmiersprache und die Laufzeitumgebung. Mit dem JSR 241 standardisiert der JCP erstmals eine weitere Sprache für diese Umgebung: Groovy. Im Gegensatz zur Java-Sprache erlaubt Groovy dynamische Konstruktionen, wie man sie sonst nur von Sprachen wie Ruby, Python, Lisp oder Smalltalk kennt. Unsere Groovy-Serie zeigt Ihnen, wie Sie Ihre Java-Projekte mit Groovy elegant ergänzen können.

Die Grundlage des Erfolgs von Java ist weniger die Sprache als vielmehr die Verbreitung und Qualität der Laufzeitumgebung (JRE). Sie zeichnet sich durch eine gute Abstraktion der darunter liegenden Maschinenarchitektur aus und erlaubt damit den Einsatz von Komponenten, Frameworks, Services und ganzen Softwarelösungen auf sehr unterschiedlichen Betriebssystemen. Die Java-Plattform ist sehr stabil und lässt sich sauber und effizient betreiben. Auf dieser Grundlage sind mittlerweile so viele nützliche Funktionalitäten geschaffen worden, dass man sich eine Programmierwelt ohne sie nur schwer vorstellen kann. Auf der Java-Plattform sind Accessibility, Security, Internationalisierung und vieles andere mehr eine Selbstverständlichkeit bis hin zu komplexen Architekturen und Frameworks wie EJB, JDO, Hibernate, Spring, etc.

Andererseits ist die Java-Sprache - verglichen mit z.B. Ruby - weniger elegant und ausdrucksstark. Wer möglichst kompakte, wiederverwendbare, duplikationsarme, änderungsfreundliche und übersichtliche Systeme bauen will, wird mit Java mehr Mühe haben als mit Ruby. Das Typ- und Objektmodell von Java und der Methoden-Dispatch am statischen Typ erlauben zwar viele Laufzeit-Optimierungen. Das wiederum geht aber zu Lasten der Konsistenz und Flexibilität:

  • Primitive Datentypen sind keine Objekte
  • Operatoren gibt es nur für primitive Datentypen aber nicht für Objekte (Ausnahme: '+' für Strings)
  • Eigenschaften und Methoden von Klassen können sich zur Laufzeit nicht ändern
  • Parametrisierung mit Logik wird mühsam (Anonyme Inner Classes vs. Closures)

Schon fordern manchen Rubyisten, Java solle jetzt "in Würde abtreten". Ruby fehlt aber gerade das, was Java so stark macht: die Plattform.

Die diesjährige JavaOne war eine eindrückliche Bestätigung des Konzepts der Java-Plattform mit buchstäblich milliardenfacher Verbreitung sowie der Förderung von neuen Sprachen für diese Plattform mit den JSRs 241 (Groovy), 274 (BeanShell), 223 (Scripting for the JVM) und 292 (Bytecode Instruction "invokeDynamic"). Frank Cohen prägte den Begriff der "Scripting JavaOne", um die Präsenz des Themas zu verdeutlichen mit nicht weniger als sechs Veranstaltungen zum Thema Groovy. Rod Copes Groovy Präsentation war ein Glanzlicht der Konferenz und die einzige Veranstaltung, bei der ich spontanen Szenenapplaus für die Live-Demo erlebt habe.

Groovy: Javas dynamischer Freund

Erfolgreiche agile Systeme bieten auf einem verlässlichen Fundament eine Kombination von statischen und dynamischen Anteilen. Erstere schaffen eine verbindliche Struktur, Letztere erlauben flexible, intelligente Konfigurationen, domänenspezifische Ausdrücke und dynamische Eingriffsmöglichkeiten.

Dynamik durch Scripting

Es gibt zwei Mittel, um Java mit Groovy zu dynamisieren: Scripting und Meta-Programmierung. Beim Scripting ist der auszuführende Groovy-Code zur Java-Compilezeit noch unbekannt und wird erst zur Laufzeit dynamisch ermittelt.
Die dynamische Ermittlung kann sehr unterschiedlich aussehen. Der Code kann zum Beispiel vom Benutzer zur Laufzeit eingegeben werden. Stellen Sie sich einen Funktionsplotter vor, bei dem der Benutzer die anzuzeigende Funktion spezifiziert. Der Groovy-Code steht nach der Eingabe in einem Java String.
Angenommen, der Benutzer gibt die Funktion "Math.sin(x)" ein. Dann lässt sich für jedes x aus dem Wertebereich der Funktionwert y auf folgendem Weg berechnen:

// Java
for (double x=0; x Double y = (Double) groovy.util.Eval.x(new Double(x), benutzerEingabe);
}

Die Eval Klasse stellt einige statische Bequemlichkeitsmethoden, wie beispielsweise x(Object input, String code) zur einfachen Evaluation von Groovy-Code, zur Verfügung. Sie gibt Resultate vom Typ Objectzurück. Deshalb benötigen wir den Typecast auf Java-Seite.
Andere Einsatzgebiete für Scripting sind Spreadsheet-Funktionalitäten mit der Eingabemöglichkeit für Formeln, Konsolen für Administratoren zur Analyse und Manipulation von langlaufenden (Server-)Prozessen bis hin zu Wiki-Seiten, in die der Benutzer selbst Groovy-Code einbetten kann (siehe SnipSnap [4]).
Auszuführender Groovy-Code kann nicht nur aus Benutzereingaben stammen. Genauso gut kann er in Datenbanken hinterlegt sein, aus (Konfigurations-)Dateien gelesen, über das Netz bezogen oder als Java-String programmatisch erzeugt werden.

Die Bezeichnung Skriptsprache leitet sich von dieser Art des Scripting ab. Es sind ca. 200 (!) Skriptsprachen für die JVM bekannt [1]. Sie unterscheiden sich nicht nur durch ihre Syntax, sondern vor allem durch die konzeptionelle Verbindung mit Java. Im Gegensatz zu vielen anderen Kandidaten basiert Groovy auf der gleichen Laufzeitarchitektur wie Java. Das betrifft nicht nur Bytecode-Kompatibilität für die JVM, sondern auch die Durchgängigkeit der beteiligten Teil-Architekturen für Security, Profiling, Debugging, usw.

Auf dieser Grundlage kann Groovy sehr enge Integrationsoptionen mit Java anbieten, die es unter anderem erlauben, den Groovy-Code mit eingeschränkten Java Security Permissions auszuführen. Das ist notwendig, um zu verhindern, dass ein Benutzer zum Beispiel System.exit(0) in einem Serverprozess evaluieren lässt.
Als einfachste Integrationsmöglichkeit steht GroovyShell zur Verfügung. Diese Klasse erlaubt das Lesen von Groovy-Code aus unterschiedlichen Datenquellen, Parsing des Codes, Caching des erstellten Scripts und Datenübergabe mittels der Binding-Klasse. GroovyShell ist für kleinere, in sich abgeschlossene Skripte geeignet.
Wenn Skripte wachsen, stellt sich bald der Wunsch ein, sie in mehrere Skripte (und z.B. Dateien) aufzuteilen. Skripte, die wiederum von anderen Skripten abhängig sind, lassen sich am besten mit der GroovyScriptEngine evaluieren. Sie verwaltet eine Liste von URLs, innerhalb derer die abhängigen Skripte gesucht werden - analog zum Verfahren eines ClassLoaders.

Die allumfassende Lösung bietet der GroovyClassLoader. Er stellt zur Laufzeit Groovy-Code als Java-Klassen zur Verfügung, d.h. als Objekte vom Type java.lang.Class,ohne dass *.class Files entstehen. Standardmäßig wird der Groovy-Code in *.groovy-Dateien gesucht, man kann aber auch einen eigenen ResourceConnector verwenden, um den Groovy-Quellcode z.B. aus einer Datenbank zu laden. Der GroovyClassLoader hält einen Cache der generierten Klassen, der aktualisiert wird, sobald sich die Sourcen ändern. Auf diese Weise erhält man eine vollkommen transparente Integration ohne Kompromisse bei der dynamischen Anpassbarkeit.

Klassischerweise werden Skriptsprachen in die JVM über das Apache Bean Scripting-Framework (BSF) integriert. In Java 6 (Mustang) wird dieses Verfahren durch den JSR 223 abgelöst. Groovy unterstützt beide Verfahren, die aber leider im Gegensatz zum GroovyClassLoader keine Kontrolle der Security Permissions erlauben. Zu guter Letzt lässt sich Groovy-Code mittels Spring integrieren. Projekte, die auf Spring basieren, können Groovy-Code direkt als Spring Beans ansprechen.

Neben diesen dynamischen Integrationsmöglichkeiten kann man Groovy auch statisch mit Java zusammenführen, indem man den Groovy-Quellcode mit dem Programm groovyc in *.class-Dateien kompiliert. Diese Klassen lassen sich von Java aus genauso verwenden wie Klassen, die aus Java-Quellcode erzeugt wurden.
Groovy ist aber nicht nur dynamisch ausführbar, es ist vor allem in sich dynamisch.

Dynamik durch Meta-Programmierung

Objekte haben Zustand und Verhalten. Die Belegung der Felder definiert den Zustand des Objekts, die verfügbaren Methoden definieren das Verhalten. In Java ist für jede Klasse zur Compile-Zeit die Menge aller Felder und Methoden vollständig bekannt. Bei der dynamischen Programmierung mit Groovy wird diese Beschränkung aufgehoben.
Nehmen wir z.B. die Groovy-Expando-Klasse, die uns als Sparringspartner dienen soll. Sie kennt weder Zustand noch Verhalten, die spezifisch für einen Boxer sind, wir können es ihr aber zur Laufzeit beibringen.

def boxer = new Expando()
boxer.nimmDas = 'Aua!'
assert 'Aua!' == boxer.nimmDas
boxer.wehrDich = { mal -> this.nimmDas * mal }
assert 'Aua!Aua!Aua!' == boxer.wehrDich(3)

Wir weisen der Property nimmDas den Wert "Aua!" zu, obwohl in der Implementierung von Expando kein solcher Zustand explizit vorgesehen ist. Wir können sogar dynamisch die Logik zuweisen, wie sich der Boxer wehren soll: als x-faches Austeilen dessen, was er vorher eingesteckt hat.
Was hier wie eine Spielerei aussieht, eröffnet tatsächlich viele nützliche Anwendungen. Unmittelbar einleuchtend ist die Verwendung in Tests, um abhängige Objekte durch dynamische Stubs zu ersetzen, ohne dafür neue Klassen schreiben zu müssen.

Die dynamischen Methoden bilden auch die Grundlage für Groovys innovative Umsetzung der Builder Patterns, mit denen man so unterschiedliche Dinge bauen kann wie GUIs, XML-Dokumente, Knotenstrukturen oder Ant Tasks.(Abb. 1).



Abb. 1: Swing GUI per Groovy Builder

Listing 1 zum Beispiel erzeugt das Swing Interface von Abbildung 1 mithilfe eines SwingBuilder. Das SwingBuilder-Objekt swing interpretiert die Methodenaufrufe frame, menuBar, panel, label usw. nicht nur für die Erzeugung des entsprechenden Swing-Objekts (Factory Methode), sondern auch für den Aufbau der Enthaltenseins-Beziehung (Building Containment).

SwingBuilder
def swing = new groovy.swing.SwingBuilder()
def frame = swing.frame(title:'JavaMagazin Demo') {
    menuBar {
        menu 'Datei'
    }
    panel {
        label 'Rutscher:'
        slider()
        comboBox(items: ['eins','zwei','drei'])
    }
}
frame.pack()
frame.show()

Solche eleganten Konstruktionen lassen sich nur mir dynamischen Sprachen erreichen. Groovy benutzt zu diesem Zweck eine MetaClass, die zu jeder Groovy-(und Java-)Klasse Informationen über die verfügbaren Eigenschaften und Methoden speichert und Eingriffe in den Methoden-Dispatch erlaubt.

Alle nicht privaten Methodenzugriffe werden durch die zugehörige MetaClass geschleust. Darauf aufbauend realisiert Groovy weitere vereinfachte Dynamisierungsmöglichkeiten: Abfangen von Methodenaufrufen durch Implementierung der Methode invokeMethod, das Hinzufügen von ganzen Methodensätzen (Categories) zu fremden Klassen mit dem use-Konstrukt und nicht zuletzt die standardmäßige Erweiterung vieler JRE-Klassen um neue Methoden (GDK).

Die dynamischen Eigenschaften von Groovy sind vergleichbar (wenn auch nicht identisch) mit Mixins in Ruby oder AOP. Das anschaulichste Beispiel für den Nutzen dieser Eigenschaften zeigt Grails [3], das Groovy-Applikationsframework.

Fazit

Ob man nun Groovy-Code von Java aus aufruft oder Java-Objekte von Groovy aus verwendet: Beide Arten der Integration bereichern Java um dynamische Ausdrucksmöglichkeiten. Noch haben wir nicht beschrieben, wie Groovy-Code genau aussieht. Die Groovy-Syntax wird im nächsten Teil dieser Serie vorgestellt. Darauf folgt eine Sammlung ausgewählter Anwendungsbeispiele. Den Abschluss bildet eine Einführung in Grails.

Weitere Infos zum Thema JSR 223 finden Sie im Java Magazin 6.2006, Siegrfried Göschl: Eine neue Erkenntnis. JSR 223 - Scripting for Java: die Verwendung des Java Scripting API in der Praxis.

Dierk König ist Softwareentwickler bei Canoo Engineering in Basel, Committer in den Projekten Groovy und Grails, sowie Autor des Buchs "Groovy in Action".
 

Kommentare

Ihr Kommentar zum Thema

Als Gast kommentieren:

Gastkommentare werden nach redaktioneller Prüfung freigegeben (bitte Policy beachten).