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 6. 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 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 Spezifikation ist für Java-EE-Verhältnisse angenehm kurz: ca. 100 gut lesbare Seiten1.
Neben der Referenzimplementierung JBoss Weld2 stehen u. a. Apache OpenWebBeans3 und Resin CanDI4 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 und die Plattformdienste, sind aber weitgehend abgegrenzt (Abb. 2.1).
Abbildung 2.1: Anwendungskomponenten
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, denn die aufrufende Komponente muss die benutzte sehr genau kennen, Änderungen sind aufwändig, der Einsatz einer alternativen Komponente unmöglich.
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 zu Tage 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 als Injektionsziel dienen.
CDI Beans
Die Anforderungen an CDI Beans sind denkbar gering: Nahezu jede konkrete Java-Klasse ist dazu geeignet5. 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 Bean6
CDI beachtet allerdings nicht alle Klassen im Classpath. Zusätzlich zu den Klassen selbst ist eine Datei namens beans.xml notwendig, die komplett leer sein darf. Sie muss im Verzeichnis META-INF eines Jar-Files oder eines Classpath-Verzeichnisses oder im Verzeichnis WEB-INF einer Webanwendung stehen, um die zugehörigen Klassen für CDI sichtbar zu machen. 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://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.3: Effektiv leerer CDI-Deskriptor „beans.xml“
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 @Inject7 annotiert (Listing 2.4).
public class DemoModel
{
@Inject
private GreetingBean greetingBean;
public String getHelloWorld()
{
return this.greetingBean.getGreeting() + ", Welt!";
}
Listing 2.4: Injektion in eine Instanzvariable
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 Objekts übereinstimmen. Jede CDI-Bean hat potenziell mehrere Bean Types, nämlich die Klasse selbst, alle Basisklassen und alle direkt oder indirekt implementierten Interfaces. Die Klasse CocktailMockRepository in Listing 2.5 hat somit drei Bean Types: CocktailMockRepository, CocktailRepository und Object.
public class CocktailMockRepository implements CocktailRepository
{
public void insert(Cocktail cocktail) { … }
public Cocktail findById(String id) { … }
…
}
Listing 2.5: CDI-Bean als Implementierung eines Interfaces
Als Injektionstyp kann somit auch eine Basisklasse oder ein Interface dienen, womit eine weitere Entkopplung der CDI Beans untereinander stattfindet. Man könnte also später bspw. den konkret injizierten Typ verändern, ohne die Injektionsstelle anpassen zu müssen (Listing 2.6).
public class CocktailModel
{
@Inject
...