Direkteinspritzer

Die Java Specification Requests 330 und 299 haben es kurz vor Toresschluss in die Java-EE-6-Spezifikation geschafft. Gemeinsam bilden sie die Grundlage für die typsichere Injektion von Abhängigkeiten. Die nicht unumstrittenen JSRs haben den Anspruch, die besten Funktionen aus etablierten Frameworks wie Seam, Guice und Spring zu vereinen und als Standard zu etablieren.

In Pocket speichern vorlesen Druckansicht 66 Kommentare lesen
Lesezeit: 15 Min.
Von
  • Markus Eisele
Inhaltsverzeichnis

Dependency Injection ist eine Anwendung des Inversion-of-Control-Prinzips (IoC) und als solches ein Entwurfsmuster für die objektorientierte Softwareentwicklung. Es dient dazu, die Abhängigkeiten von Objekten und Komponenten zu verringern, indem Referenzen per Deklaration oder Annotation übergeben und nicht innerhalb der Objekte selbst erzeugt werden. IoC überträgt die Verantwortung für das Anlegen und Verknüpfen von Objekten an eine externe Stelle.

Dadurch erlangt der Code eines Objekts Unabhängigkeit von seiner Umgebung sowie von der Umsetzung der Klassen. Schon Java EE 5 bot erste Ansätze von Dependency Injection für Container-Ressourcen, die sich jedoch auf die Annotationen @Resource, @PersistenceContext, @PersistenceUnit und @EJB beschränkten. Man konnte sie in andere vom Container verwaltete Objekte injizieren, etwa in Servlets, JSF Backing Beans und andere EJBs. Im Vergleich mit Dependency-Injection-Frameworks wie Googles Guice, Spring oder Seam blieben diese Funktionen allerdings weit hinter denen von den Entwicklern gewünschten zurück. Größtes Manko: die fehlende Kooperation mit Objekten, die nicht der Container verwaltet. Das Anlegen von einfachen Datenzugriffsobjekten war so nicht möglich; die Spezifikation empfahl, auf EJBs auszuweichen. Dieser Sachverhalt wiederum beeinträchtigte die Kompatibilität mit Bibliotheken von Drittanbietern.

Bei der Veröffentlichung von Java EE 6 waren diese Schwierigkeiten natürlich bekannt [1]. Dennoch hat es bis kurz vor dem Finale im August 2009 gedauert, bis die zuständige Expert Group nach langem Ringen die Aufnahme zweier zusätzlicher Spezifikationen verkündete. Es handelte sich um den JSR 330 (Dependency Injection for Java) und um den JSR 299 (Contexts and Dependency Injection for the Java EE Platform, CDI), Letztere auch bekannt unter dem Namen Web Beans. Ersteren riefen im Mai 2009 Rod Johnson von SpringSource und Bob Lee von Google ins Leben. Gavin King von RedHat initiierte den JSR 299.

Die Gründe für die eingeschränkte Akzeptanz der beiden besagten JSRs sind vielfältig. Langwierige Diskussionen gab es um den JSR 299, der bereits Ende Mai 2006 seinen Weg in den Java Community Process (JCP) antrat. Die Kommentare aus der Java-Gemeinschaft drückten eine Unmenge an Wünschen, Anmerkungen und Vorbehalten aus. Vor allem zeigten die Mitglieder Angst vor der zunehmenden Komplexität der Java-EE-Spezifikation durch ein weiteres Komponentenmodell.

Erst einige Klarstellungen im JSR sowie der Namenswechsel in „Contexts and Dependency Injection for the Java EE Platform“ konnten die vielfach politisch motivierten Vorbehalte soweit entkräften, dass der Aufnahme in den übergeordneten JSR 316 („Umbrella“) nichts mehr im Wege stand. Wegen der späten Entscheidung blieben für die eigentliche Arbeit, nämlich die Integration in die anderen involvierten JSRs, nur zwei Monate. Zeitgleich mit diesen Vorgängen entstand der JSR 330, der zum Ziel hat, die Dependency Injection Annotations für die Java-SE-Plattfom zu standardisieren. JSR 330 und der JSR 299 zeigen deutliche Parallelen – ein Umstand, der mit verantwortlich sein dürfte für den langwierigen Entscheidungsprozess in der Java EE 6 Expert Group.

JSR 299 bietet ein neues Set an Funktionen für Java EE. Grundlage ist eine Menge von definierten Lebenszykluskontexten. Darauf aufbauend bietet der JSR Modularisierung, übergreifende Aspekte (Decorators und Interceptors) sowie typsicheres Injizieren von Objekten, alles Dinge, die Java-EE-Komponenten zu einer geschmeidigen Kooperation bewegen sollen. Das Paket erlaubt das einheitliche Binden von Objekten (JSF Managed Beans, EJBs et cetera) an definierte Kontexte. Als Basis dienen die Annotationen aus dem JSR 330 (beispielsweise @Inject, @Qualifier und @ScopeType). JSR 330 beschreibt nur ein minimalistisches Set an Dependency-Injection-Funktionen, insbesondere fehlen Java-EE-spezifische Kontexte wie Request, Session und Conversation. Ähnlich wie die Java Persistence API (JPA, [2]) JDBC verwendet, nutzt JSR 299 den JSR 330. Es bleibt allerdings jedem unbenommen, den einen ohne den anderen einzusetzen. Wer sich mit Springs Angeboten zur Dependency Injection auskennt, dürfte schnell merken, dass CDI moderner wirkt. Denn sie ist typsicher, und man konfiguriert sie durch Annotationen. Für den Entwickler birgt CDI eine neue und komplexe Materie.

Im Zentrum von Java EE 6 stehen die neuen Managed Beans 1.0. Sie belegen bisher keinen eigenen JSR, sondern sind als separates Dokument frisch in die Java-EE-Spezifikation eingeflossen. Eine Managed Bean ist ein einfaches Java-Objekt (POJO) mit klar umrissenem Lebenszyklus. Es kann die Annotationen @PostConstruct und @PreDestroy verwenden und gibt sich selbst über die Annotation @ManagedBean zu erkennen. Darüber hinaus sollen, beginnend mit Java EE 6, langfristig alle Container-Objekte als Managed Beans antreten. Zurzeit gilt dies nur für JSF Backing Beans und EJB Session Beans. Die Container stellen für ihre Managed Beans verschiedene Dienste bereit; im Falle der Session Beans beispielsweise Thread-Sicherheit und Transaktionen. Innerhalb der CDI finden sich also keinerlei übergreifende Dienste wie Security, Remoting oder Messaging. Die müssen und sollen ausschließlich von den jeweiligen Containern bereitgestellt werden. Einzig die Dependency-Injection-Fähigkeiten für alle Managed Beans bietet die CDI selbst an (siehe Abbildung).

In Java EE 6 dreht sich alles um die Managed Beans, die künftig sämtliche Container-Objekte ablösen sollen.

Dank dieser Konstruktion ist es erstmals möglich, EJBs als JSF Managed Beans einzusetzen. Parallel gibt es noch einige direkte Integrationspunkte in andere JSRs. So lassen sich Managed Beans auch in der Expression Language (EL) verwenden. Und darüber sind sie wiederum mit View-Techniken wie JSP und Facelets verbunden. JPA-Annotationen, etwa @PersistenceContext und @PersistenceUnit funktionieren jetzt in allen Managed Beans, ebenso wie @EJB und @Resource.

Für die ersten Gehversuche mit den neuen Funktionen bieten sich verschiedene Wege an. Vollständig Java-EE-6-konform und final verfügbar ist GlassFish v3. Abseits der Kombination des Applikationsservers mit der Entwicklungsumgebung NetBeans 6.8 gibt es wenige Alternativen. Die Referenzimplementierung für den JSR 299 ist JBoss Weld; sie steht unter der Apache License Version 2.0. Man kann sie sowohl als Teil von JBoss Seam 3 als auch separat bekommen. Neben GlassFish unterstützt Weld JBoss’ Applikationsserver 6, Tomcat 6 und Jetty 6.1. In dieser Liste fehlen bislang die kommerziellen Applikationsserver. Als Referenzimplementierung für den JSR 330 dient das ebenfalls unter Apache-Lizenz erhältliche Guice.

Die folgenden Beispiele basieren auf einem einfachen Webprojekt, angelegt mit NetBeans und GlassFish. Grundsätzlich ist jede Java-Klasse mit einen no-argument oder einem mit @Inject markierten Konstruktor eine Managed Bean. Dazu gehören alle Java Beans und Stateless Session Beans. Wer die Dienste der CDI nutzen will, muss sie zusammen mit einer speziellen Markierungsdatei (beans.xml) in einem Modul (WAR, EAR, JAR) verpacken. Fehlt das Modul, passiert schlicht nichts, es folgt aber eine aufwendige Fehlersuche. Der einfachste Fall einer Managed Bean:

@Named 
@RequestScoped
public class HelloWorld {
...
}

Die @Named-Annotation führt dazu, dass eine neue Instanz von HelloWorld erzeugt und im Request Scope abgelegt wird. Darauf zugreifen kann der Entwickler per Expression Language beispielsweise in Facelets:

#{helloWorld} 

oder per Injektion aus anderen Klassen heraus:

@Inject 
HelloWorld helloWorld;

CDI kennt vier verschiedene Scopes, die sich um eigene Kreationen erweitern lassen. @RequestScoped, @SessionScoped und @ApplicationScoped gehören schon lange zum Java-EE-Standard. Der Request Scope beheimatet die Objekte mit der Lebensdauer einer Anfrage. Der Session Scope hält Objekte für die Länge einer Browser-Session und der Application Scope für die Dauer einer Anwendung. Neu hinzugekommen ist der @ConversationScoped, eine abgespeckte Session, die von der Anwendung aktiv gesteuert werden muss und sich nur auf ein Browserfenser beziehungsweise Tab bezieht. Eine Conversation soll ein abgeschlossenes Stück Arbeit im Sinne des Nutzers kapseln. Listing 1 zeigt, wie das bezogen auf eine Bestellung aussieht.

Mehr Infos

Listing 1: Conversation Scope

@ConversationScoped
public class BestellManager {
@Inject
private Conversation conversation;
@PersistenceContext
private EntityManager em;
public Bestellung bestellungErstellen() {
Bestellung b = new Bestellung();
conversation.begin();
return b;
}
public void positionHinzufügen(Position p) {
//[...]
}
public void bestellungSpeicher(Bestellung b) {
em.persist(b);
conversation.end();
}
}

Abseits der eingebauten Scopes findet man noch einen Pseudo-Scope namens Dependent. Hier landen alle Objekte, die nicht ausdrücklich einen Scope angeben. Clients tauschen niemals eine Instanz aus diesem Scope aus. Er gehört quasi einem abhängigen, anderen Objekt, und der Container erzeugt ihn erst, wenn dieses ihn benötigt. Mit dem Verschwinden des instanziierenden Objektes erlischt auch das abhängige.

CDI schaut beim Zugriff auf einen injizierten Typen nach allen Klassen, die sich im Classpath befinden und Kandidaten für die Injektion sind. Grundsätzlich ist es verboten, in einer Deployment Unit zwei gleichnamige Managed Beans zu beherbergen. Sind zwei verschiedene Implementierungen eines Typs notwendig, etwa eine JPA- und eine JDBC-Version eines Datenzugriffsobjektes, müssen sie sich unterschieden lassen. Das geschieht mithilfe sogenannter Qualifier, mit denen der Entwickler eigene Annotationen erstellt, die den gewünschten Typen näher beschreiben (Listing 2).

Mehr Infos

Listing 2: Qualifier-Annotation

// Qualifier Annotation
@Qualifier
@Target({TYPE})
@Retention(RUNTIME)
public @interface JdbcOrder {}
//OrderDao Interface
public interface OrderDao {}
//Die JDBC basierte OrderDao Implementierung
@JdbcOrder
public class JdbcOrderDao implements OrderDao {//...}
//Die JPA basierte OrderDao Implementierung
public class JPAOrderDao implements OrderDao {//...}
//Injection per Qualifier
@Inject
private @JdbcOrder OrderDao dao;

Dieses Beispiel demonstriert die Vorteile der Typsicherheit. Bisher ließen sich solche Anforderungen nur per stringbasierten @Resource(name=““, type=java.lang.Object)-Annotationen erfüllen. Dieses Prozedere ist leider anfällig für Tippfehler, die meist erst sehr spät auffallen. Nun finden die Prüfungen bereits in der IDE zur Entwicklungszeit statt.

Bislang ließen sich lediglich Beans in andere Objekte injizieren. Sie mussten darüber hinaus einen Konstruktor haben. Das Konzept der Producer schließt die Lücke zu Objekten, die keine Beans sind sowie solchen, die umfassendere Initialisierungen verlangen, als sie über Konstruktoren zu leisten wären. Producer erlauben es grundsätzlich, jede JDK-Klasse als Bean zu exponieren. So erreicht man Polymorphie zur Laufzeit, was mit Qualifiers nicht geht. Am Beispiel eines Zufallszahlengenerators lässt sich das verdeutlichen: Die Klasse in Listing 3 liefert eine Zufallszahl mit der Methode getRandomNumber().

Mehr Infos

Listing 3: Zufallsgenerator

public class RandomNumberGenerator {
private Random random = new Random(System.currentTimeMillis());
@Produces @Named int getRandomNumber() {
return random.nextInt(100);
}

Die Methode selbst ist mit der @Produces- und der @Named-Annotation versehen, was im konkreten Fall dazu führt, dass ein Objekt mit dem Namen randomNumber im Dependent Scope abgelegt und bei jedem Aufruf durch ein anderes Objekt erneut aufgerufen wird. Die @Disposes-Annotation bildet den Gegenpart zu @Produces und ermöglicht das geordnete Zerstören der erzeugten Objekte.

Schon seit Java EE 5 lassen sich Interceptors im Rahmen der EJB-Spezifikation verwenden. Ab Java EE 6 und in Verbindung mit der CDI steht diese Option für alle Managed Beans zur Verfügung. Sie gewährleistet ein einfaches, annotationsgetriebenes Vorgehen. Es gibt zwei Interceptors: Die technische Variante lässt sich an Lifecycle-Methoden des jeweiligen Containers binden, (etwa @PostConstruct, @PreDestroy), die fachliche Ausprägung bindet man an beliebige Methoden von Managed Beans. Das klassische Beispiel für die Anwendung von Interceptors ist Logging und Tracing. Es beginnt mit der Definition des Interceptor Bindings:

@InterceptorBinding 
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface LogTime {}

Im Anschluss daran wird der eigentliche Interceptor erstellt. Die erste Annotation verbindet ihn mit dem Interceptor Binding (Listing 4).

Mehr Infos

Listing 4: Interceptor Binding

@LogTime
@Interceptor
public class LoggingInterceptor {
@AroundInvoke
public Object logExecutionTime(InvocationContext ic) throws Exception { //...
}

Danach bringt der Entwickler den Interceptor per Annotation an die gewünschte Stelle. Möchte er beispielsweise die Laufzeit des ersten Code-Beispiels verfolgen, fügt er einfach die Annotation @LogTime hinzu. Im Gegensatz zu den EJB Interceptors muss der CDI Interceptor allerdings vorher in die beans.xml eingetragen werden. Dies geschieht mit:

<interceptors>   
<class>de.ix.LoggingInterceptor</class>
</interceptors>

Dabei stößt man erstmalig auf XML-Deklarationen, was zunächst wie ein Schritt zurück wirkt. Dennoch hat dieses Vorgehen positive Aspekte. Ohne die Einträge in der beans.xml ignoriert der Container die Interceptors. Der Entwickler kann also per Konfiguration entscheiden, ob und welche er in Stellung bringen will – das war bislang nicht möglich. Jede Klasse darf beliebig viele Interceptors besitzen. Jeder wird mit seiner eigenen Binding-Annotation an die gewünschte Stelle verfrachtet. Ebenfalls neu ist die Möglichkeit, eine Hierarchie aufzubauen: Ein Interceptor Binding Interface kann selbst wieder Interceptors verwenden. Während diese Konstrukte das System technisch unterstützen, kümmern sich die Decorators um die fachlichen Belange. Als Interceptor-Variante beziehen sie sich nur auf definierte Java-Typen, arbeiten also entlang eines Interface.

Dependency Injection und Interceptors gewährleisten die lose Kopplung in Systemen auf Source-Code-Ebene. Das Event-Konzept geht noch einen Schritt weiter und erlaubt die Interaktionen von Klassen ohne jegliche Abhängigkeiten zur Compile-Zeit. Dazu bietet die CDI einen eigenen Mechanismus. Startpunkt ist ein Event-Objekt, eine einfache Java-Klasse (POJO), die Informationen vom Sender zum Empfänger transportiert. Sie benötigt weder Annotationen noch besondere Interfaces, sondern muss lediglich alle für den Transport vorgesehenen Attribute sowie einen entsprechenden Konstruktor enthalten:

public class HelloEvent { 
public HelloEvent(String message) {
// ...
}
}

Der Event-Sender (Producer) bekommt dann eine Instanz der Klasse Event vom Typ HelloEvent injiziert und löst daraufhin das Ereignis aus:

@Inject Event<HelloEvent> events; 
public void hello(){
events.fire(new HelloEvent
("Ein hallo Event");
}

Ist dies geschehen, benötigt man nur noch einen Empfänger (Consumer). Das Empfangen eines Ereignisses kann innerhalb einer beliebigen Managed Bean passieren und wird über die Annotation @Observes erledigt:

public class HelloWorldListener {
public void listenToHello
(@Observes HelloEvent helloEvent) {
//...
}
}

Mittels Qualifiers lassen sich auch hier Unterscheidungen im Hinblick auf einen konkreten Typ treffen. Für die Empfänger legt der Entwickler die notwendigen Zustellbedingungen fest. Events unterstützen auch transaktionales Verhalten.

Die Kombination der beiden JSRs 299 und 330 liefert einen komplett neuen Funktionsblock für Java EE 6. In der Praxis fallen die Unterschiede zwischen beiden JSRs nicht ins Gewicht, ihre Fähigkeiten sind gut aufeinander abgestimmt. Die Abstimmungsprotokolle des JSR 316 zeugen allerdings von einer bewegten Vergangenheit beider Spezifikationen. Kritische Geister aus der Java Community bezweifeln nach wie vor die Praxistauglichkeit der Kandidaten. Beispielsweise stimmte IBM im Java Community Process gegen die finale Version des JSR 299. Man glaube nicht an die reibungslose Zusammenarbeit der Spezifikationen, wolle aber trotzdem an ihrer Weiterentwicklung mitwirken.

Mit der CDI entfällt ein großer Anteil an Code, der bisher nötig war, um Weboberflächen mit geschäftlichen Abläufen zu verknüpfen. Die direkte Referenzierbarkeit von EJBs als Managed Beans ist der größte Vorteil. Selbst wenn man die Tragfähigkeit des JSR 299 infrage stellt: Das richtungsweisende Potenzial verbirgt sich sowieso woanders, nämlich in den besagten Managed Beans. In Zusammenarbeit mit einer neuen Form der Dependency Injection werden die Entwickler zukünftig wohl alle Java-EE-Komponenten nur noch per typsicherer Annotationen an ihre Container binden.

arbeitet im Bereich Applied Technology Research im Center of Competence IT-Architecture der msg systems AG in München.

[1] Markus Eisele, Java-Entwicklung; Warp 6; Java EE 6 nimmt Masse und Geschwindigkeit auf; iX 1/2010, S. 66

[2] Markus Eisele; Java-Entwicklung; Überholtes Getriebe; Java Persistence API 2.0: Fortgeschrittenes Mapping; iX 3/2010, S. 114

Außerdem in diesem Heft: Artikel zur Bean-Validierung mit JSR 303 sowie zu Javas Content Repository API (JSR 170/283). Nicht zu vergessen die Titelgeschichte dieses Hefts: eine umfangreiche Gegenüberstellung von Java EE 6 und .Net 4.0. Gleich hier bestellen oder im Zeitschriftenhandel holen! (jd)