Dependency in Java EE 6

CDI
Kommentare

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 Seiten. Neben der Referenzimplementierung JBoss Weld stehen u. a. Apache OpenWebBeans und Resin CanDI als CDI-Container zur Verfügung. Doch 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. 1.1).

Abb.1.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 1.1)?

// Unsicher: Ist ein Objekt mit dem Namen konfiguriert?
//           Falls ja, hat es den korrekten Typ?
MyService myService
  = (MyService) jndiContext.lookup("ejb/myService")

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. 1.2).

Abb. 1.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.

[header=Bereitstellung und Injektion von Beans]

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 Bean sind denkbar gering: Nahezu jede konkrete Java-Klasse ist dazu geeignet. 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 1.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";
  }
}

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 1.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>

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 @Inject annotiert (Listing 1.4).


public class DemoModel
{
  @Inject
  private GreetingBean greetingBean;

  public String getHelloWorld()
  {
    return this.greetingBean.getGreeting() + ", Welt!";
  }


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 1.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) { … }
  …
}


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 1.6).

public class CocktailModel
{
  @Inject
  private CocktailRepository cocktailRepository;
  …

Die Injektion muss eindeutig auflösbar sein. Es darf also in der Anwendung nicht zwei CDI Beans geben, die den verlangten Bean Type haben. Konflikte führen spätestens beim Deployment der Anwendung im Applikationsserver zu Fehlermeldungen, werden also nicht erst zur Laufzeit bei Benutzung des entsprechenden Programmcodes erkannt. Einige Entwicklungswerkzeuge erkennen Konflikte bereits zur Entwicklungszeit und geben entsprechende Warnungen aus.

Damit ist der oben skizzierten Flexibilität bei der Wahl der konkreten Implementierung zunächst einmal ein Riegel vorgeschoben: Es kann in der Beispielanwendung nicht mehrere CDI Beans geben, die CocktailRepository implementieren. Wir werden später Mechanismen kennen lernen, die dies doch ermöglichen und darüber hinaus eine Auswahl der genutzten Bean erlauben (siehe Kapitel 1.5 Qualifier und 1.6 Alternatives).

Der Bean Type einer CDI Bean kann ihr explizit zugewiesen werden. Dazu wird die Klasse mit @Typed annotiert und als Wert der Annotation werden eine oder mehrere Typen angegeben. Die Bean hat dann nur die derart explizit genannten Bean Types (Listing 1.7).

@Typed(CocktailJdbcRepository.class)
public class CocktailJdbcRepository implements CocktailReposi-tory
{
  …

@Typed kann auch ohne Parameter verwendet werden. Eine so annotierte Klasse hat keinen Bean Type und ist somit sozusagen aus dem Spiel.

Method Injection

Neben der beschriebenen Möglichkeit der Injektion in Instanzvariablen kann man bei der Erzeugung von CDI-Bean-Objekten auch Methoden aufrufen lassen. Die Spezifikation spricht hier von Initializer Methods. Da es aber auch Lifecycle-Methoden gibt, die im Rahmen der Initialisierung von Objekten ablaufen, wird im Folgenden der ebenfalls übliche Begriff der Methodeninjektion verwendet.

Eine CDI Bean kann beliebig viele Methoden enthalten, die mit @Inject annotiert sind. Sie dürfen allerdings nicht abstrakt, statisch oder generisch sein. Diese Methoden werden im Zuge der Initialisierung von CDI-Bean-Objekten aufgerufen, wobei die Methodenparameter durch Injektion mit den passenden Werten versorgt werden. Für jeden einzelnen Parameter gelten dabei die gleichen Regeln wie für die Instanzvariablen der Field Injection. Die Injektion geschieht also genau genommen nicht in die Methode, sondern in die Methodenparameter. Statt der Injektion in die Instanzvariable im Beispiel aus Listing 1.4 hätte somit auch eine Methode verwendet werden können (Listing 1.8).

public class DemoModel
{
  @Inject
  public void setGreetingBean(GreetingBean greetingBean)
  {
    this.greetingBean = greetingBean;
  }
  …

Das Beispiel zeigt den recht üblichen Fall einer Setter Injection, d. h. der Injektion in eine Setter-Methode. Darauf ist Method Injection aber nicht eingeschränkt: Die Methode darf einen beliebigen Namen haben und beliebig viele Parameter annehmen. Sie muss nicht public sein und darf einen Return-Wert liefern (der im Beispiel aber nicht verwendet wird).

Die hier besprochenen Methoden dürfen natürlich auch direkt vom Programmcode aufgerufen werden. Die Injektionsannotationen haben dann aber keinerlei Bedeutung.

Constructor Injection

Was mit einer Methode geht, kann auch für einen Konstruktor genutzt werden: Eine CDI Bean darf einen Konstruktor besitzen, der mit @Inject annotiert ist. Dieser wird dann statt des bisher genutzten Konstruktors ohne Parameter zur Instanziierung von CDI-Objekten verwendet. Für die Parameter des Konstruktors gelten wieder die oben erläuterten Injektionsregeln. Die Construktor Injection ist somit eine weitere Alternative zu den bisherigen Injektionsmöglichkeiten (Listing 1.9).

public class DemoModel
{
  private GreetingBean greetingBean;

  @Inject
  public DemoModel(GreetingBean greetingBean)
  {
    this.greetingBean = greetingBean;
  }
  …

Es darf immer nur ein Konstruktor mit @Inject ausgezeichnet sein. Er darf beliebig viele Parameter haben und muss nicht public sein. Gibt es keinen solchen Konstruktor, wird der parameterlose Konstruktor verwendet.

Werden die Konstruktoren direkt aufgerufen (mit new o. ä.), so werden die dadurch entstehenden Objekte nicht durch den CDI-Container gemanagt, d. h. es finden in ihnen keine Injektionen statt und die Lebensdauer der Objekte unterliegt nicht der Steuerung durch den Container.

Bean Name

Wir haben gesehen, dass die Zuordnung von CDI-Objekten bei der Injektion allein über ihren Bean Type geschieht, wodurch Typsicherheit garantiert wird. Man kann CDI Beans allerdings auch einen Namen zuordnen, um damit die Referenz aus einer ungetypten Umgebung heraus zu ermöglichen. So bieten bspw. JavaServer Pages und JavaServer Faces eine Expression Language an, mit der aus der textbasierten Definition einer Webseite auf benannte Java-Objekte zugegriffen werden kann.

Die Definition des Bean-Namens geschieht durch Annotation mit @Named. Der dabei übergebene String gilt dann als Name der annotierten Bean. Wird die Annotation ohne Parameter verwendet, gilt der einfache Klassenname mit kleinem Anfangsbuchstaben als Bean Name. In Listing 1.10 ist @Named also äquivalent zu @Named(„demoModel“).

@Named
public class DemoModel
{
  …
  public String getHelloWorld() { … }
  …

Eine derart benannte CDI Bean kann mithilfe der erwähnten Expression Language aus einer Webseite auf Basis von JSP (Listing 1.11) oder JSF (Listing 1.12) referenziert werden.

<%@ page language="java" … %>
<html>
<body>
  ${demoModel.helloWorld}
  …
<html xmlns="http://www.w3.org/1999/xhtml" … >
<h:body>
  <h:outputText value="#{demoModel.helloWorld}" />
  …

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -