Dynamische Freundschaft

Java-Entwickler, die auf die Vorzüge einer dynamischen Programmiersprache nicht verzichten wollen, können mit Groovy Java um leistungsstarke Sprachkonzepte erweitern. Mit Grails gibt es zudem ein Framework, das auf Java-Anwendungen aufbaut, selbst aber komplett offen ist.

vorlesen Druckansicht 10 Kommentare lesen
Lesezeit: 13 Min.
Von
  • Dierk König
Inhaltsverzeichnis

Dynamische Programmiersprachen erobern sich ihren Platz in der professionellen Softwareentwicklung. Microsoft bietet mit IronPython eine Skriptsprache für die .Net-Plattform an, und Sun unterstützt unter anderem Groovy, um die Attraktivität der Java-Plattform neu zu beleben. Groovy wird über den JSR-241 standardisiert, der eine Sprachspezifikation, eine Referenzimplementierung und ein Test Compatibility Kit umfasst. Die finale Version 1.0 der Referenzimplementierung ist für September geplant.

Mehr Infos

Auf der diesjährigen JavaOne gab es sechs Veranstaltungen, die Groovy ganz oder zum Teil gewidmet waren. Mit Java 6 sind verbesserte Integrationsmöglichkeiten für Fremdsprachen (JSR-223) und sogar Erweiterungen der Virtual Machine zum Aufruf dynamischer Methoden (JSR-292) geplant. Nicht zuletzt hat die Popularität von Ruby on Rails Sun hier unter Zugzwang gesetzt.

Die Einsatzmöglichkeiten dynamischer Sprachen sind vielfältig und bieten gerade im Java-Umfeld eine sinnvolle Ergänzung. Für die Art und Weise, wie Groovy in einem Java-Projekt helfen kann, haben sich bestimmte Verwendungskonzepte (Patterns) herauskristallisiert, die dieser Artikel beschreibt. Sie gelten zum Teil auch für andere der circa 200 Sprachen, die auf der Java-Plattform einsetzbar sind (siehe „Onlinequellen“).

Es gibt eine Unmenge von Frameworks, Komponenten und Bibliotheken, die in Java geschrieben sind. Viele haben eine hohe Qualität und gelten in der Industrie als Standard. Groovys enge Integration mit Java erlaubt es, deren Funktionen schnell und flexibel zu einer Applikation zusammenzufassen.

Eine Mini-Applikation, die einen RSS-Feed der BBC ausliest und die Schlagzeilen in einem Swing-GUI präsentiert, soll dies verdeutlichen (Listing 1). Die verwendeten Bausteine sind XML-Parser, Java Networking und die Swing-Widget-Bibliothek, die alle zum Lieferumfang von Java und Groovy gehören, sodass keine weitere Installation erforderlich ist. Sprachmittel von Groovy binden die Applikation zusammen.

Mehr Infos

Listing 1

def base  = 'http://news.bbc.co.uk/rss/newsonline_uk_edition/'
def url = base +'front_page/rss091.xml'
def items = new XmlParser().parse(url).channel[0].item

def swing = new groovy.swing.SwingBuilder()
def frame = swing.frame(title: 'Top 10 BBC News') {
scrollPane {
table() {
tableModel(list: items[0..9]) {
closureColumn(header: 'title',
read: { item -> item.title.text() } )
closureColumn(header: 'text',
read: { item -> item.description.text() } )
}
}
}
}
frame.pack()
frame.show()

Die ausgelesenen Schlagzeilen des RSS-Feed präsentiert Groovy in einer Swing-Oberfläche.

Bis auf wenige Besonderheiten können auch Programmierer die Funktionsweise des Beispiels leicht nachvollziehen, die Groovy noch nie gesehen haben. Der XML Parser untersucht den Inhalt einer URL, wobei er hinter den Kulissen selbst für die notwendige Verbindung sorgt. Vom XML-Inhalt selektiert er das erste channel-Element und von diesem alle item-Elemente. Damit ist der anzuzeigende Datenbestand extrahiert.

Nachfolgend baut das Programm eine Swing-Oberfläche mit einem Frame und darin eingebetteten ScrollPane und Table. Für den Table verwendet es ein TableModel mit speziellen Groovy TableColumns, die mit ihrer eigenen Ausführungslogik (Closures) parametrisiert sind. Die interne Logik sorgt für einen lesenden Zugriff auf das Modell (read). Die Closure erscheint in geschweiften Klammern und bekommt pro Tabellenzeile das zugehörige Business-Objekt aus dem Modell als item injiziert. Bei der Konstruktion des Modells hat der Range-Subscript-Operator die Liste der Objekte auf die Teilliste der ersten zehn Elemente eingeschränkt. Da es sich bei den Objekten noch immer um XML-Elemente handelt, kann man in der read-Closure direkt auf ihre Subelemente title und description zugreifen und deren Text-Repräsentation auswerten. Ein explizites return ist optional, denn Closures geben immer das Ergebnis des letzten Ausdrucks zurück. Wie oft bei dynamischen Sprachen ist die Beschreibung des Codes deutlich länger als der Code selbst.

Groovy ist aus der Beobachtung heraus entstanden, dass die heutige Applikationsentwicklung mit Java zu großen Teilen aus der Zusammenführung von vorgefertigten Komponenten mit Frameworks besteht, aber eine einfache und flexible Verbindungssprache bisher nicht vorhanden war. Java an sich ist dafür zu spröde, eignet sich jedoch hervorragend für die Erstellung von Bausteinen. Es ist sicher kein Zufall, dass viele Java-Programmierer in ihren Projekten zunächst ein weiteres Framework bauen, bevor sie mit der eigentlichen Applikationsentwicklung beginnen. Das „Alleskleber“-Pattern legt die Vermutung nahe, dass dynamische Sprachen nur auf der obersten Ebene des Software-Stacks benötigt werden - weit gefehlt!

Das Herz jeder Anwendung bildet das Domänen- oder Business-Modell mit seinen Entitäten, deren Beziehungen, ihrem Verhalten sowie den damit verbundenen Regeln. Verglichen mit technischen Modellen ist das Verständnis fachlicher Modelle schnellerer Veränderung unterworfen. Leider fehlen oft die programmiersprachlichen Mittel, diesen Erkenntnisfortschritt in der Applikation mitzuvollziehen, weil die Software bei weitem nicht so soft ist, wie sie sein könnte.

Wie bildet man zum Beispiel eine Bonusregelung ab, die unterschiedliche Sätze je nach erzieltem Umsatz gewährt (Listing 2)? Solch ein Groovy-Herzstück lässt sich in eine Datei auslagern, um Anpassungen leicht zu ermöglichen. Eine Java-Applikation kann diese Spezifikation der Bonusregelung dynamisch auswerten, etwa wie in Listing 3.

Mehr Infos

Listing 2

umsatz = mitarbeiter.umsatz
switch(umsatz / 1000) {
case 0..100 : return umsatz * 0.04
case 100..200 : return umsatz * 0.05
case {it > 200} : bonusClub.add(mitarbeiter)
return umsatz * 0.06
}

Listing 3

Binding binding = new Binding();
binding.setVariable("mitarbeiter", mitarbeiter);
binding.setVariable("bonusClub", bonusClub);
GroovyShell shell = new GroovyShell(binding);
File script = new File(filename);
float bonus = (float) shell.evaluate(script);

Die Java-Applikation (Listing 3) wertet die Groovy-Datei mit der Bonusregelung (Listing 2) aus.

Bei dieser Art der Integration handelt es sich nicht nur um einfache Berechnungen. Die Verwendung von bonusClub zeigt, dass auch Operationen auf den verfügbaren Java-Objekten möglich sind. Das führt unvermeidlich zu den wichtigen Fragen von Security und Performance. Groovy beachtet beide in vollem Umfang. Als Java-Bytecode unterliegt die Skriptsprache der Security-Architektur der Virtual Machine. Alle denkbaren Restriktionen gelten genau wie bei Java-Code (Security Manager, Permissions). Bei der Ausführungsgeschwindigkeit ist ausschlaggebend, wann und wie oft die VM ein Script liest und in ausführbaren Code umsetzt.

Hier bietet Groovy eine Fülle von Kombinationsmöglichkeiten bis hin zu einem eigenen Classloader, der Groovy-Scripts transparent als Java-Klassen bereitstellt. Die Eingriffsmöglichkeiten gehen aber noch viel weiter.

In der Medizin spricht man von minimal-invasiver Chirurgie, wenn der Arzt eine Operation an den „Interna“ des Patienten lediglich durch eine schlüssellochgrosse Öffnung vornehmen kann. Ähnlich kann Groovy durch einen kleinen Einstiegspunkt laufende Programme untersuchen und verändern, ohne sie erst narkotisieren (restart) zu müssen. Bei lang laufenden Server-Prozessen ist das besonders interessant.

Hat beispielsweise ein Onlineshop sporadisch Probleme mit dem Datenbankzugriff, kann der Administrator über einen Einstiegspunkt folgenden Groovy-Code absetzen, um die Datenbank-Verbindung mit einer DebugConnection zu dekorieren:

def ds = Config.dataSource
ds.connection =
new DebugConnection(ds.connection)

Voraussetzung ist lediglich, dass es eine Klasse Config mit einer statischen Methode getDataSource() gibt, die ein Objekt zurückliefert, das die Methoden getConnection() und setConnection(connection) bereitstellt.

Ab jetzt zeichnet die Applikation die Datenbankzugriffe schön auf. Angenommen, es stellt sich heraus, dass es jemand durch Manipulation geschafft hat, einen negativen Preis zu nennen, um kostenlos an die Ware zu kommen. In dem Fall kann der Administrator live im Server eingreifen, um diesen Benutzer aus dem ServletContext zu verbannen. Für den Fall, dass alle Benutzer im user-Attribut aufgelistet sind, kann er Folgendes an den Server schicken - davon ausgehend, dass ein Servlet den Code evaluiert:

users = 
servletContext.getAttribute(‘users’)
bad = users.findAll{
user -> user.cart.items.any{
it.price < 0}}
servletContext.setAttribute(
‘users’, users - bad)

Die zweite Anweisung liest sich so: Finde alle user in der users-Liste mit einem cart, das mindestens ein item hat, dessen price kleiner als null ist. Diese bad user entfernt die nachfolgende Zeile aus der Kollektion aller user.

Diese Ausdrücke bezeichnet man in Groovy als GPath in Anlehnung an die XPath-Ausdrücke für XML. Sie erlauben eine einfache Navigation durch Objektgrafen und sind extrem flexibel. Der obige Code evaluiert beispielsweise user.cart. Das funktioniert nicht nur, wenn das user-Objekt eine getCart()-Methode hat, sondern auch, wenn user eine Map referenziert, in der unter dem Schlüssel cart ein Cart-Objekt abgelegt ist. Nota bene: Es handelt sich hier um Objekte, die in Java geschrieben sind.

Das Pattern der endoskopischen Operation ist nur für Experten gedacht. Es gibt aber ein vergleichbares Entwurfsmuster für Endbenutzer: die Personalisierung.

Viele Applikationen und Web-Portale lassen sich weitgehend an persönliche Bedürfnisse und Vorlieben anpassen. Mit Groovy kann man solche Konfigurationsmöglichkeiten leicht zur Verfügung stellen. Die Wikis SnipSnap, XWiki, biscuit und andere machen es vor. Sie erlauben dem Benutzer, Groovy in seine Seiten einzubetten und dabei sowohl auf die internen Objekte als auch auf alle externen Ressourcen zuzugreifen. So werden das Personalisieren der internen Suchfunktion oder das Einbinden eines externen RSS-Feed zur leichten Übung.

Mächtige Personalisierungsmöglichkeiten bieten auch Makro-Systeme, wie man sie von den verbreiteten Office-Applikationen her kennt. Die Kombination von Command Pattern und Groovy-Scripts ermöglichen das Aufzeichnen, Wiederholen und Bearbeiten von Automatisierungsschritten in einer Java-Applikation. Automatisierungen müssen dabei nicht einmal lokal ablaufen. Oracle bietet zum Beispiel für die Cluster-Administration eine Schnittstelle zur Java Management Extension (JMX) mit in Groovy geschriebenen MBeans an und stellte dieses Verfahren auf der JavaOne vor.

Fragt sich, wo die Offenheit ihre Grenzen hat. Immerhin gibt es erstaunlich viele Applikationen, bei denen buchstäblich alles konfigurierbar ist. Eigentlich zählen dazu alle typischen Web-Applikationen, die vollständig in einer Skriptsprache geschrieben sind. Wer sich heute eine Blog-Applikation in Python, ein Ruby-Wiki, ein in PHP implementiertes Auktionssystem oder einen Perl-Webshop installiert, kann jederzeit jede beliebige Code-Zeile ändern, ohne sich groß über Kompilation und Klassenpfade Gedanken machen zu müssen.

Vergleichbares ermöglicht Groovy auf der Java-Plattform, mit dem Unterschied, dass man auf einer Java-Grundlage aufbaut, deren Code-Basis unangetastet bleiben soll. Das Grails Web-Framework ist ein perfektes Beispiel dafür. Es basiert auf Spring und Hibernate, die in Java geschrieben sind und als solide Grundlage dienen. Alle Grails-Applikationen selbst sind jedoch komplett offen. Das Domänenmodell, die Views, Controller und Services werden vollständig in Groovy geschrieben und sind allen Anpassungen zugänglich. Ein besonderer Fall komplett in Groovy erstellter Anwendungen sind die kleinen Helferlein, die das Leben der Softwareentwickler durch Automation erleichtern.

Rund um die eigentliche Entwicklung gibt es in einem Projekt viele Aufgaben, die man gerne automatisieren möchte. Dazu zählen der Build, die kontinuierliche Integration, Deployment, Installationen, Server-Überwachung, Reports, Statistiken, Erzeugen von Dokumentation und nicht zuletzt die Automation der funktionalen Tests.

In diesem Umfeld kommen viele verschiedene große und kleine Tools zum Einsatz, darunter Ant, das mit mächtigen Implementierungen und kompakten Beschreibungsmöglichkeiten glänzt. Was dem Build-Werkzeug jedoch fehlt, sind einfache programmatische Eingriffsmöglichkeiten. Groovy schließt diese Lücke nicht nur durch eigene Ant-Tasks, sondern vor allem durch den AntBuilder, der diese Tasks direkt als Objekte erzeugt und ausführt. Damit ist das Script der bedingten und iterativen Ausführung zugänglich, um zum Beispiel eine Menge an Mails zu verschicken (Listing 4).

Mehr Infos

Listing 4

def users = [ 
[name:'Dierk', email:'dierk.koenig@canoo.com'],
[name:'Other', email:'other@no.such.server']
]
def ant = new AntBuilder()

for (user in users) {
ant.mail(
mailhost: 'my.email.server',
subject : 'build ist fertig') {
from(address: 'my@email.com')
to (address: user.email)
message( """
Hallo ${user.name},
Der Build ist mal wieder fertig:
${new Date().toGMTString()} """ )
}
}

Der AntBuilder erzeugt Groovys Ant-Tasks als Objekte und ermöglicht eine iterative Ausführung - hier das Verschicken mehrerer Mails.

Die users-Variable referenziert eine Liste, jeder darin gespeicherte user eine Map mit den Schlüsseln name und email. Wenn der AntBuilder ant die Methode mail aufruft, erzeugt er den Ant-Mail-Task, parametrisiert ihn, erzeugt die eingeschachtelten from-, to- und message-Elemente, verknüpft diese mit dem Mail-Task und führt ihn anschließend aus. Die Verdreifachung der String-Begrenzer erlaubt es der Deklaration, über mehrere Zeilen zu fließen, und doppelte statt einfache Anführungszeichen sorgen für die Evaluierung eingebetteter Ausdrücke.

AntBuilder ist ein gutes Beispiel dafür, was dynamische Sprachen von solchen unterscheidet, die Java lediglich um Ausführbarkeit durch Scripts ergänzen: Sie sind in der Lage, mit beliebigen Methodenaufrufen umzugehen. So gibt es im AntBuilder keine mail()-Methode. Er registriert lediglich den Aufruf und fragt die taskdef nach der zugehörigen Klasse.

Groovys dynamische Fähigkeiten gehen weit über das Vorspiegeln von Methoden hinaus und erlauben es, beliebigen (auch Java-) Objekten neue Methoden, Operatoren und Properties zuzuweisen, Methodenaufrufe abzufangen, umzuleiten und sie zur Laufzeit zu verändern. Das erleichtert unter anderem Unit-Testing erheblich.

Um diese wichtigen Eigenschaften stärker herauszustellen, wird Groovy neuerdings nicht mehr als „Skriptsprache für die Java VM“ positioniert, sondern als „Java’s dynamic friend“.

Insgesamt ergeben sich durch die Einbeziehung einer dynamischen Sprache wie Groovy in die Java-Anwendungsentwicklung viele neue Erweiterungsmöglichkeiten: von der dynamischen Auswertung kleiner Schnipsel über die verschiedenen Eingriffsmöglichkeiten in ein Laufzeitsystem bis zum vollständigen Applikationsbau auf einem Java-Fundament. Zwingende Voraussetzung ist eine lückenlose Integration in das Java Runtime Environment, inklusive gemeinsamer Datentypen, Laufzeitarchitektur und Security-Modell. Groovy bietet all das plus einer langen Liste weiterer Eigenschaften, die das besondere Gefühl zurückbringen, dass Java in seinen Pioniertagen vermittelt hat: innovativ, unkonventionell, anspruchsvoll, fortschrittlich und gewürzt mit einer Prise Entdeckerstolz.

Dierk König
ist Softwareentwickler bei der Canoo Engineering AG in Basel, Committer in den Projekten Groovy und Grails sowie Autor des Buches „Groovy in Action“.

Mehr Infos

iX-TRACT

  • Groovy ist ein Open-Source-Projekt, das Class-Objekte für die Java Virtual Machine erzeugt und gleichzeitig dynamisch ausführbar ist.
  • Sun standardisiert Groovy mit dem Java Specification Request (JSR-241) als weitere Sprache für die Java-Plattform neben der Sprache Java selbst.
  • Das Open-Source-Web-Framework Grails baut auf den in Java geschriebenen Open-Source-Projekten Spring und Hibernate auf und ermöglicht dynamische Webapplikationen in Groovy auf Model-View-Controller-Basis mit transparenter Persistenz.

(ka)