2 CDI
2.1 Was ist das?
CDI steht für „Contexts and Dependency Injection for the Java EE Platform“ und ist ein Standard innerhalb des Webprofils der Dachspezifikation Java EE. Der Arbeitsbereich von CDI ist die Bereitstellung und Verknüpfung von Komponenten und Diensten als Basis für Enterprise-Applikationen. CDI ist allerdings nicht nur im EE-Umfeld nutzbar, sondern kann auch ohne Applikationsserver eingesetzt werden.
CDI 1.0 wurde im JSR 299 lange Zeit unter den Namen WebBeans entworfen und standardisiert viele Ideen und Konzepte von populären Open-Source-Frameworks wie Seam und Spring(-Core). Die aktuelle Version 1.1 (JSR 346) hat eine für Java-EE-Verhältnisse angenehm kurze Spezifikation: ca. 130 gut lesbare Seiten1.
Neben der Referenzimplemtierung JBoss Weld2 steht u. a. Apache OpenWebBeans3 als CDI-Container zur Verfügung.
2.2 Wozu braucht man das?
Professionelle Anwendungen sind nicht monolithisch aufgebaut, sondern bestehen aus Komponenten. Zum einen ergeben sich bei der Entwicklung von Software aus der Analyse der Aufgabenstellung fachliche Bereiche, die durch fachliche Komponenten abgebildet werden können. Innerhalb dieser Komponenten lassen sich wieder Teile abgrenzen, diesmal eher technischer Natur. Die Komponenten benutzen andere Komponenten sowie die Plattformdienste, sind aber weitgehend abgegrenzt (Abb. 2.1).
Eine Aufgabe der Softwareentwicklung ist es nun, diese Komponenten untereinander zu verknüpfen, sodass eine saubere Anwendungsarchitektur entsteht. Das kann natürlich mit einfachen Sprachmitteln von Java geschehen: Eine Komponente kann in ihrem Programmcode andere Komponenten instanziieren, indem sie new benutzt. Dadurch wird die Kopplung der Komponenten aber sehr stark: Die aufrufende Komponente muss die benutzte sehr genau kennen, Änderungen sind aufwändig, der Einsatz einer alternativen Komponente unmöglich.
Abbildung 2.1: Anwendungskomponenten
Zudem profitieren solche Objekte kaum von der Umgebung der Anwendung: Der Applikationsserver kennt sie nicht, kann also bspw. kein Monitoring und keine Laufzeitsteuerung dafür durchführen. Flexibler ist es, die benötigten Objekte vom Application Server herstellen zu lassen. In den früheren Versionen der Java EE – damals noch J2EE – hat man dazu weitgehend String-basierte Referenzen benutzt, hat also bspw. die benötigte Komponente per Namen im JNDI-Dienst adressiert. Hier stellt sich aber das Problem der Zielgenauigkeit: Ist ein Objekt unter dem verwendeten Namen überhaupt vorhanden und hat es den richtigen Typ (Listing 2.1)?
// Unsicher: Ist ein Objekt mit dem Namen konfiguriert?
// Falls ja, hat es den korrekten Typ?
MyService myService
= (MyService) jndiContext.lookup("ejb/myService");
Listing 2.1: Referenzierung einer Komponente über ihren Namen
Ein weiteres Problem ist die Abhängigkeit der aufrufenden Komponente von ihrer Umgebung: Der Code im Beispiel setzt unumstößlich voraus, dass es einen JNDI-Dienst gibt. Ein Test des Codes außerhalb des Applikationsservers ist damit unmöglich. Hier setzt die Idee „Inversion of Control“ an, die den aktiven Teil der Komponentenverknüpfung aus der Komponente herauslöst und in die Laufzeitumgebung – den Container – verlagert: Nicht die Komponente besorgt sich die von ihr benötigten Serviceobjekte, sondern der Container liefert sie an. Dieses Verfahren firmiert unter dem Namen „Dependency Injection“ – Injektion von benötigten Objekten –, womit wir auch schon die beiden letzten Drittel des Namens CDI erklärt hätten (Abb. 2.2).
Abbildung 2.2: Dependency Injection
Die Komponente weiß jetzt nicht mehr, woher sie die von ihr genutzten Objekte erhält. Damit ist die Kopplung zu ihrer Umgebung so klein geworden, dass ein Austausch leicht möglich wird: Im Produktivsystem werden Komponenten und Ressourcen vom Container bspw. weiterhin im JNDI-Dienst verwaltet, während sie in einer Testumgebung ohne Container von der Testklasse geliefert werden.
Bei der Dependency Injection obliegt es dem Container, wann die benötigten Objekte erzeugt und zerstört werden, er kann also die Komponenten von der kompletten Lifecycle-Steuerung entlasten. Damit sind wir beim ersten Drittel des Namens CDI: Die injizierten Objekte können Kontexten zugeordnet werden, die über ihre Lebensdauer bestimmen. So können die von einem Geschäftsprozess genutzten Services inklusive der darin verwalteten Daten sitzungsorientiert gehalten werden.
Die geschilderten Konzepte sind beileibe nicht neu. Sie haben vielmehr seit vielen Jahren Einzug in die Java-Softwarelandschaft gehalten und dort ihren Nutzen unter Beweis gestellt – stark unterstützt insbesondere durch das Spring-Framework, das damit wesentliche Schwächen der damaligen J2EE adressierte. Neu ist allerdings ein Aspekt von CDI, der die beschriebene lose Kopplung um Typsicherheit ergänzt: Durch weitgehenden Verzicht auf Objektnamen und Verwendung von Java-Typen an ihrer Stelle wird erreicht, dass sich die Komponentenverdrahtungen schon sehr früh – zur Compile-Zeit, spätestens zur Deployment-Zeit – prüfen lassen und somit Fehler nicht erst zur Anwendungslaufzeit zutage treten.
2.3 Bereitstellung und Injektion von Beans
Die durch CDI miteinander verknüpften Klassen werden in der CDI-Spezifikation Managed Beans genannt. Im Folgenden wird der Begriff CDI Bean bevorzugt, da Managed Beans auch in anderen Teilen der Java EE auftauchen. CDI Beans können sowohl injizierte Objekte darstellen als auch Injektionsziele enthalten.
2.3.1 CDI Beans
Die Anforderungen an CDI Beans sind denkbar gering: Nahezu jede konkrete Java-Klasse ist dazu geeignet4. Benötigt wird nur ein Konstruktor ohne Parameter (wir werden später sehen, dass auch Klassen mit anderen Konstruktoren CDI Beans sein können). Die Klasse GreetingBean aus Listing 2.2 ist somit als CDI Bean verwendbar.
public class GreetingBean
{
public String getGreeting()
{
int hourOfDay = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
if (hourOfDay < 10)
return "Guten Morgen";
else if (hourOfDay < 18)
return "Guten Tag";
else
return "Guten Abend";
}
}
Listing 2.2: Einfache CDI Bean5
CDI beachtet allerdings nicht alle Klassen im Classpath. Durch einen Deskriptor namens beans.xml kann die sog. Bean Discovery gesteuert werden. Die Datei darf komplett leer sein und muss im Verzeichnis META-INF eines JAR-Files, eines Classpath-Verzeichnisses oder im Verzeichnis WEB-INF einer Webanwendung stehen, um die zugehörigen Klassen für CDI zu aktivieren. Da viele Entwicklungswerkzeuge über leere XML-Dateien meckern, sollte aber das in Listing 2.3 gezeigte wohlgeformte XML-Dokument statt der leeren Datei verwendet werden. Hierin können dann später auch einfacher Ergänzungen vorgenommen werden.
<?xml version="1.0"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all" version="1.1">
</beans>
Listing 2.3: Effektiv leerer CDI-Deskriptor „beans.xml“
Das Attribut bean-discovery-mode lässt bereits vermuten, dass man mit dem Deskriptor genauer beeinflussen kann, welche Klassen als CDI Beans erkannt werden. Zudem ist der Deskriptor beans.xml nicht mehr in allen Fällen notwendig. Darauf gehe ich später noch genauer ein.
Listing 2.4 zeigt den Deskriptor für die CDI-Version 1.0. Hier musste der Deskriptor für Webanwendungen im Verzeichnis WEB-INF stehen. Seit 1.1 ist auch WEB-INF/classes/META-INF erlaubt, was die Platzierung im Vergleich zu anderen Anwendungstypen vereinheitlicht.
<?xml version="1.0"?>
<beans xmlns=http://java.sun.com/xml/ns/javaee
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>
Listing 2.4: CDI-Deskriptor für die alte Version CDI 1.0
2.3.2 Field Injection
Die Nutzung einer derart bereitgestellten Klasse in einer weiteren CDI Bean kann durch Injektion in ein Feld der Bean geschehen. Dazu wird die betroffene Instanzvariable mit @Inject6 annotiert (Listing 2.5).
public class DemoModel
{
@Inject
private GreetingBean greetingBean;
public String getHelloWorld()
{
return this.greetingBean.getGreeting() + ", Welt!";
}
Listing 2.5: Injektion in eine Instanzvariable
2.3.3 Bean Type
Es fällt auf, dass zur Injektion kein Name o. ä. verwendet wird, sondern offensichtlich allein der Typ des Injektionsziels für die Zuordnung ausreichend ist. Das ist ein entscheidendes Konzept, das die Injektion nicht passend getypter Objekte verhindert. Der Typ des Injektionsziels muss mit einem Bean Type des injizierten...