CDI: Typsichere pluggable Injection

Nachdem im vorherigen Beitrag über CDI, die „Contexts Dependency Injection“ in Java EE 6, die Basics wie Deklaration und Injection einer Bean behandelt wurden, so liegt der Fokus diesmal auf den flexiblen Mechanismen, mit denen CDI genutzt werden kann. Die Beschreibung ist dabei nicht allumfassend, sondern soll nur einen Einblick bieten, welche Vorteile die Nutzung von CDI in Komponenten-Architektur und -Implementierung bringt.

Zur Demonstration wird ein kleiner Rechenautomat entwickelt, der nach und nach um mehrere Rechenoperationen erweitert wird. Ein abstraktes simples Beispiel, welches mehrere Standard-Technologien kombiniert: Bean Validation, JavaServer Faces 2 mit Facelets, und natürlich CDI. Die Codebeispiele sind teils sinnvoll verkürzt. Die komplette lauffähige Web Application (WAR) steht am Ende des Beitrags verlinkt als Download bereit.

Oberfläche aus dem Java EE 6 Baukasten

Zum Testen der Funktionen wird JSF2 genutzt, welches ebenfalls zum leichtgewichtigen Java EE 6 Web Profile gehört und somit neben CDI immer zur Verfügung steht. Die Oberfläche bietet die Möglichkeit zwei Zahlen einzugeben. Die Zahlen können anschliessend per Knopfdruck addiert werden. Alternativ können „alle Operationen“ ausgeführt werden – was sich hierhinter versteckt, lichtet sich später. Die Seite wird standardkonform per XHTML (seit JSF2) wie folgt definiert:

<h:form>
    Value1:
    <h:inputText value="#{calculationService.input.value1}" 
        id="input1"/>
    <h:message for="input1" />

    Value2:
    <h:inputText value="#{calculationService.input.value2}"
        id="input2"/>
    <h:message for="input2" />

    <h:commandButton
        action="#{calculationService.performAddition}"
        value="addition" />
    <h:commandButton
        action="#{calculationService.performAll}"
        value="all operations" />
</h:form>
<ui:repeat value="#{calculationService.output}" var="line">
    <h:outputText value="#{line}" />
</ui:repeat>

Es werden zwei Eingabefelder h:inputText definiert, welche beide an Properties einer Bean calculationService.input gebunden werden. Über h:commandButton werden die angesprochenen Features, nämlich Addition und „alle Operationen“, angeboten. Schlussendlich werden über den Facelets-Loop ui:repeat die Ausgabezeilen aus dem calculationService ausgelesen und dargestellt. Vielleicht ist es aufgefallen: Für beide Eingabefelder gibt es jeweils ein h:message Block. Diese Blöcke stellen eventuell aufgetretene Validierungsmeldungen attributsspezifisch dar.

Validation Constraints im Entity

Die Fehlermeldungen der Validierung werden zwar automatisch generiert, erfordern jedoch Validierungsregeln. Daher ein Blick auf die Bean, in welche über die Eingabefelder geschrieben wird:

public class CalculationValue implements Serializable {
    @NotNull
    private Long value1;
    @NotNull
    private Long value2;
}

Man sieht hier Standard JSR-303 Bean Validation Annotations. In diesem Fall wird geprüft, ob in den Werten der Bean überhaupt Daten hinterlegt wurden. Die Bean Validation gehört ebenfalls zum Java EE 6 Web Profile. JSF2 prüft in der Process Validation Lifecycle Phase auf Bean Validation Constraints. Die Constraints werden dabei direkt in der Entity definiert, genau wie in diesem Beispiel. Zusätzlich wird die Bean Validation auch in nachgelagerten Schichten, zum Beispiel in einem JPA2 Persistency Layer, sichergestellt.

Addition per CDI

Die CalculationValue Entity ist als Property input in der Bean calculationService hinterlegt. Ausserdem werden bei den Button-Actions Methoden aus der Bean calculationService aufgerufen. Höchste Zeit also, einen Blick hinein zu werfen:

@Named
@SessionScoped
public class CalculationService implements Serializable {

    private List<String> output;

    @Inject
    private transient Addition addition;

    @Inject
    @LargeValue
    private CalculationValue input;

    public String performAddition() {
        output.clear();
        output.add( addition.operate(input) );
        return "index";
    }

Der CDI-geschulte Betrachter sieht natürlich sofort, dass es sich hierbei um eine Bean aus dem CDI Kontext handelt. Per @SessionScoped wird ihre Lebensdauer auf die Benutzer-Session erweitert. Dies ist nur für dieses Beispiel so gewählt und keine Voraussetzung. Der Standard @RequestScoped bzw. keine Angabe funktioniert ebenso. Die Bean kann ohne weitere Konfiguration direkt im JSF2 EL genutzt werden.

Als nächstes fällt die Injection @Inject einer Addition Bean auf. Diese Bean kapselt die Logik der Addition. Genau diese Logik wird in der performAddition() Methode aufgerufen. Dabei wird der Addition der Eingabewert input übergeben, welcher zuvor aus der JSF2 View gesetzt wurde. Die Addition wird dabei value1 und value2 aus input addieren. Klingt logisch, oder?

Eine Bean, viele Produzenten

Das Datenmodell input ist eine Instanz vom Typ der oben eingeführten CalculationValue Entity. Dafür benötigt man kein CDI, eine normale Deklaration ohne Annotations wäre völlig ausreichend. Trotzdem wird dem Member @Inject und @LargeValue vorangestellt. In diesem Beispiel ist das Ziel, eine explizite Initialisierung des Members zu erreichen. Die Werte von input werden zwar vom JSF2 Formular beim Submit gesetzt, allerdings werden bereits bei der Anzeige des Formulars die aktuellen Werte von input dargestellt. Es handelt sich hier also schlicht um das Setzen von Default Werten.

An dieser Stelle wird eine Entkopplung mittels CDI etabliert. Genauer gesagt sieht man hier einen Weg, das Factory-Pattern zu ersetzen. Durch das @Inject sucht CDI nach Möglichkeiten, die gewünschte Bean zu instanziieren. Das kann wie bereits geschehen per @Named direkt in der Bean-Klasse ermöglicht werden, eine mächtigere Variante aber ist, eine Methode zur Instanziierung zu nutzen:

@Named
public class ValueProducer {

    @Produces
    @LargeValue
    public CalculationValue initLarge() {
        final CalculationValue input = new CalculationValue();
        input.setValue1(2048L);
        input.setValue2(1024L);
        return input;
    }
    
    @Produces
    @SmallValue
    public CalculationValue initSmall() {
        final CalculationValue input = new CalculationValue();
        input.setValue1(6L);
        input.setValue2(2L);
        return input;
    }    
}

Diese Klasse (selbst CDI Bean) bietet zwei Methoden, welche beide mit @Produces annotiert sind. Durch diese Annotation weiss CDI, dass die Methoden CDI-Beans vom jeweiligen Return-Typ der Methode (hier: CalculationValue) herstellen kann. Dieses Wissen wird bei Injections genutzt, sofern keine passende Bean-Instanz bereits im Kontext vorhanden ist. Das heisst: Wenn eine @Inject Annotation gefunden wird, wird zuerst im Kontext geprüft ob bereits eine passende Bean Instanz vorliegt. Falls nein, wird die @Produces annotierte Methode aufgerufen und die dort erzeugte Bean injected. Dabei kann in der Methode natürlich auch komplexe Logik zur Generierung der Bean angewandt werden.

Nun wurden im ValueProducer jedoch zwei Methoden mit @Produces versehen. Und zu allem Unglück geben sogar beide Methoden den gleichen Typ zurück, beides mal CalculationValue! Welche Methode wird denn nun aufgerufen?

Bean-Auslese

Dieser Fall ist gar nicht so abwegig wie man denken könnte. Man stelle sich zum Beispiel vor: Zwei Datenbanken werden von einer Applikation genutzt. Nun könnte man Connection Instanzen injecten. Je nach Fall muss die Connection dann zur ersten oder zur zweiten Datenbank verbinden; trotzdem sind beide Klassen vom selben Typ.

CDI bietet hier eine elegante, weil typsichere Lösung. Im Beispiel werden die zwei produzierenden Methoden mit den Annotationen @LargeValue und @SmallValue unterschieden. Die gleichen Annotationen werden dann bei der Injection der Bean benutzt. Somit kann gesteuert werden, welche Methode zur Instanziierung genutzt wird. Trotzdem bleibt die Entkopplung erhalten: Der ValueProducer wird von keiner Klasse importiert.

Was aber ist @LargeValue eigentlich für eine Annotation? Eine eigene! Und die sieht so aus:

@Target( { ElementType.TYPE, ElementType.METHOD, 
ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
public @interface LargeValue {
}

Wie man sieht, hält sich der Umfang doch in Grenzen. Aus CDI ist hierbei nur der @Qualifier Bezeichner, welcher CDI signalisiert dass diese Annotation zur Typisierung einer Bean dient. Der Code von @SmallValue sieht, bis auf den Namen, identisch aus.

Mittels @Produces Methoden kann also eine hohe Dynamik erreicht werden. Um trotzdem gezielt die passenden Beans herauszufiltern, bieten sich eigene @Qualifier an. Dies erhöht auch die Lesbarkeit des Codes, da die eigenen Annotationen auch entsprechend der Business Domain benannt werden können, z.B. @TopOffer.

Warum nicht einfach alles?

Eine noch höhere Flexibilität kann durch die Injection von Instanzlisten erzielt werden. Dies ist die gegenteilige Richtung von der gezielten Injection mittels @Qualifier : Es werden alle Beans im CDI Kontext eines Typs injected. Standard-Case für dieses Feature ist ein Plugin Mechanismus: Es kann eine Library eingebunden werden, welche das entsprechende Interface implementiert und sich dem CDI Kontext z.B. per @Named bekannt macht. Diese Implementierung ist dann überall verfügbar, ohne Wissen über die eigentliche Klasse oder Library.

Eine Erweiterung des Beispiels bringt dies näher. Der Rechenautomat soll jetzt endlich sein „alle Operationen“ Feature erhalten. Dabei werden alle im Automat verfügbaren Rechenoperationen nacheinander angewandt. Bisher ist dies nur die Addition, daher wird der Automat nun um neue Beans für Subtraktion, Multiplikation und Division erweitert. Alle diese Beans (auch die bereits existierende Addition) implementieren folgendes Interface:

public interface CalculationOperation {
    public String operate(CalculationValue input);
}

Es gibt nun vier CalculationOperation Implementierungen, welche alle via @Named im CDI Kontext als Bean registriert sind.

Diese Implementierungen werden gesammelt im CalculationService herangezogen und in der JSF Action Methode genutzt:

    ...
    @Inject
    private transient
        Instance<CalculationOperation> operations;
    
    public String performAll() {
        output.clear();
        for (CalculationOperation operation: operations) {
            output.add(operation.operate(input));
        }
        return "index";
    }

Instance ist ein Generic aus CDI. Die Injection mittels Instance zieht alle Instanzen im CDI Kontext dieses Typs heran. Weil hier ein Interface genutzt wird, werden also alle Implementierungen dieses Interfaces injected. Eine zusätzliche Einschränkung mit @Qualifier ist übrigens auch noch möglich. Nun kann man einfach über die Liste der Instanzen iterieren und die Methoden des Interfaces nutzen. Die Klasse CalculationService hat trotzdem keinerlei Referenzen auf eine der Implementierungen des CalculationOperation Interface. Es könnte nun zum Beispiel ein JAR mit zusätzlichen Implementierungen hinzugefügt werden, diese Operationen würden dann automatisch an dieser Stelle ausgeführt.

Application und Codebeispiele Download

Alle Beispiele als ausführbare Web Application (WAR) einschliesslich Quellcode finden Sie hier als Download (ZIP, 13kB). Zur Ausführung wird ein Java EE 6 Application Server benötigt (Web Profile möglich), zum Beispiel den GlassFish Server 3.0.

Das Ende und doch nur am Anfang

CDI bietet noch vieles mehr als in den Blog-Postings gezeigt werden konnte. Interceptoren beispielsweise sind ein spannendes Thema, weil hier Features wie die Transaktionalität der EJBs recht einfach nachgebaut werden kann. Trotzdem hoffe ich dass die Art, wie mit CDI in Java EE 6 gearbeitet werden kann, etwas sichtbar gemacht wurde. CDI ist eine attraktive weil leichtgewichtige Alternative zu EJB. Die duchgängige Unterstützung im schlanken Java EE 6 Web Profile macht diese Technologie für Web Applications besonders attraktiv.

Aber auch beim grossen Bruder EJB3.1 hat sich viel Spannendes getan! Mehr dazu bald an dieser Stelle.

Bis dahin beantworte ich gerne Rückfragen rund um CDI und/oder Java EE 6.

Flexible & erweiterbare jQuery Plugins

jQuery ist ein mächtiges Framework welches das Arbeiten mit JavaScript stark erleichtert. Mittlerweile ist jQuery mit Abstand das verbreitetste JavaScript Framework der Welt. Ganze UI-Elemente wie Tabs, oder Funktionsblöcke wie etwa eine Bildergalerie sucht man jedoch – bei jQuery selber – vergeblich. jQuery stellt lediglich zahlreiche mehr oder minder kleine Helfer zur Verfügung, welche es ermöglichen, solche Dinge mit geringem Aufwand zu erstellen.

Nun ist es natürlich nicht praktisch komplexe Funktionen wieder und wieder – und für jedes Projekt neu – zu schreiben. Hier kommen die „jQuery Plugins“ ins Spiel. Ein jQuery Plugin ist in der Regel eine Erweiterung von jQuery und kann beliebig komplexe Funktionen – von History Handling über UI-Elemente bis zu Lightboxen – bereitstellen.

Aufbau eines Plugins

Die jQuery Plugin API dient dazu, jQuery um neue Funktionen zu erweitern:

// jQuery um Tab Funktion erweitern:
$.fn.tabsPlugin = function(userOptions) {
   // Plugin Code...
}

Plugins sind meist sehr ähnlich aufgebaut. Sie werden in der Regel auf ein Set von HTML Elementen angewendet und man kann das Verhalten des Plugins mit Optionen etwas beeinflussen. In etwa so:

var options = {
     activeTab: 2
};
$('div.tabs').tabsPlugin(options);

Damit würde man etwas HTML Code…
2307-html Code.png

…in ein Modul verwandeln das etwa wie folgt aussieht:
2306-Tabs-thumb-500x83-2305.png

Das Plugin hat dabei meist eigene Optionen, sogenannte Defaults. Diese werden durch die Optionen, welche der Benutzer dem Plugin beim Aufruf mitgegeben hat ergänzt:

var options = $.extend({}, $.fn.tabsPlugin.defaults, userOptions);

Innerhalb des Plugins, wird danach die entsprechende Plugin-Funktion auf jedem einzelnen der übergebenen Elemente ausgeführt. Dabei muss darauf geachtet werden, dass die Funktion nur auf das jeweilige Element Einfluss hat, damit mehrere Elemente auf der Seite die gleiche Funktionalität haben können.

var $tabSets = $(this);
$tabSets.each(function(){
     // Auf jedem Element die gewünschte Funktion ausführen
     var $tabSet = $(this); // Aktuelles Tab-Set
     ...
});

Nun soll’s ein bisschen anders sein…

Ein Problem vieler Plugins ist, dass sie zwar meist ihre Aufgabe gut erledigen, nicht selten auch viele Optionen mitbringen, aber nicht flexibel oder erweiterbar sind. Nehmen wir ein Plugin, das aus einem Set an HTML Elementen ein Tab-UI generieren soll. Dieses Plugin übernimmt das Sammeln der Tabs und zugehörigen Panels, sowie das Ein- und Ausblenden der Panels beim Klick auf ein Tab. Ausserdem bringt es noch einige Optionen mit, womit der Benutzer festlegen kann, welche Klassen die ausgewählten Tabs haben sollen und welches Tab zu Beginn aktiviert werden soll. Ist doch Perfekt, nicht?

Was nun, wenn der Inhalt des Panels beim Aufruf der Seite noch nicht vorhanden ist sondern erst nachgeladen werden soll, wenn man das entsprechende Tab aktiviert? Herkömmliche Plugins versagen hier oft und gewünschte Funktionalität muss direkt im Plugin eingefügt werden. Dabei geht die Update-Fähigkeit verloren und es leidet die Wiederverwendbarkeit und Sicherheit des Projekts. Im Wissen, dass der Benutzer vielleicht mit dem Plugin noch weiter Anwendungsfälle hat neigt der Autor des Plugins dazu, möglichst viele Funktionen in sein Plugin zu stecken, da er es ja flexibel halten möchte. Damit wird es gross, schwerfällig und unübersichtlich und verfügt über eine Menge Funktionen, welche viele unter Umständen gar nicht brauchen. Der Autor des besagten Tab-Plugins muss dabei an Tastatur-Bedienbarkeit, Ajax- und History-Fähigkeit sowie vieles mehr denken.

Evented jQuery Plugins = flexibel & erweiterbar

Eine Lösung für dieses Problem lautet Custom Events oder kürzer – Evented. Dabei muss sich der Plugin Autor überlegen, welche grundlegende Funktionen das Plugin benötigt und stellt diese in einer möglichst schlichten Form zur Verfügung.

Ein Tab-UI-Element hat im Grossen und Ganzen eine Funktion: Beim Klick auf ein Tab, soll das zugehörige Panel aktiviert und alle anderen deaktiviert werden. Wir brauchen also eine „activate“ Funktion. Ausserdem müssen zu Beginn alle Tabs gesucht, und die zugehörigen Panels gespeichert werden. Dazu benötigen wir eine „setupTabs“ Funktion. Des weiteren sollten die Panels wissen, zu welchem Tab sie gehören: „setupPanels„. Beim Aufruf der Seite sollen diese beiden Funktionen ausgeführt werden und das erste Tab aktiviert werden, dazu brauchen wir noch eine letzte Funktion, die „initialize“ Funktion.

Diese Funktionen sind „Events“. Jedes übergebene Set an HTML Elementen wird mit diesen Events versehen. Da der Event damit immer zu einem Tab-Element gehört, kann damit auch das Problem von allfälligen Konflikten mit anderen Tab-Elementen einfach gelöst werden. Wenn auf einer Seite zwei Tab-Elemente vorhanden sind, beeinfluss Tab-Verbund eins den zweiten Tab-Verbund nicht, wenn darauf der activate Event ausgeführt wird:

...
$tabSets.bind("activate",
        function(e, selected) {
            // selektiertes Tab aktivieren
        }
);
...

Events nach Aussen verfügbar machen

Der Trick besteht nun darin, dass diese Funktionen nicht einfach fest in das Plugin integriert sind, sondern das jede einzelne Funktion in ein Array gepackt wird, welches über ein Objekt customEvents von aussen Zugänglich ist. Die Funktions-Array können dadurch ergänzt, oder gar ganz überschrieben werden, bevor das Plugin auf ein Set von HTML Elementen angewendet wird.

var customEvents = {
    initialize: [function(options) {
        this.bind("initialize." + options.namespace,
        function(e) {
            // Tabs und Panels vorbereiten
            //und erstes Tab Aktivieren
        });
    }],

    setupPanels: [function(options) {
        this.bind("setupPanels." + options.namespace,
        function(e) {
            // Panels mit zugehörigem Tab verbinden
        });
    }],

    setupTabs: [function(options) {
        this.bind("setupTabs." + options.namespace,
        function(e) {
            // Tabs suchen und activate Funktion bei klick ausführen
        });
    }],

    activate: [function(options) {
        this.bind("activate." + options.namespace,
        function(e, selected) {
            // Alle Tabs deaktivieren und selektiertes
            // Tab und Panel aktivieren
        });
    }]
};

Events ergänzen

Im unserem Beispiel wollten wir, dass der Inhalt des Panels erst vom Server geladen wird, wenn das Tab aktiviert wird. Die Vorgehensweise ist nun denkbar einfach, man fügt einfach vor der Plugin-Standard-Funktion eine neue Funktion hinzu, welche den Inhalt des Panels via Ajax vom Server lädt. Dabei muss man zu keiner Zeit das Plugin selber verändern. Wenn der Plugin Autor ein Bug-Fix veröffentlich, kann das Plugin einfach aktualisiert werden – die Grundfunktionen werden die gleichen bleiben und die Erweiterung wird nicht tangiert da sie sich ausserhalb des Plugins befindet.

// Neue Funktion vor die bestehende
// activate Funktion schieben:
customEvents.activate.unshift( function(options) {
    // Die Erweiterung auf das Grund-Element
    // (nicht die einzelnen Tabs) binden:
    this.bind("activate." + options.namespace,
        function(e, selectedTab) {
            // Den Inhalt des Tabs hier
            // per Ajax in das Panel laden:
            var url= selectedTab.href;
            $(selected).data('$panel').load(url);
   });
});

Der Kern des Plugins ist dabei lediglich dafür verantwortlich, dass die Default Einstellungen mit den Benutzer-Optionen ergänzt werden und dass danach alle Tab-Elemente die Custom Events erhalten.

Hello World!

Nachdem man dieses erweiterbare und flexible Tabs-Plugin geschrieben hat, ist es natürlich schön, wenn sich der Rest der Welt auch daran erfreuen kann. In meinem nächsten Post werde ich zeigen, wo und wie man ein eigenes Plugin veröffentlichen kann und was dabei zu beachten ist.