Rookie – praktische Einführung - JAXenter

Rookie – praktische Einführung

Beispiel: Feldinjizierung

Bevor es mit technischen Details weiter geht, wollen wir uns ein einfaches Beispiel mit CDI ansehen. In Listing 1 wird eine Bean Builder in die Manufacturer Bean mit der Annotation @javax.inject.Inject injiziert. Die Bean Builder besitzt dabei den Kontext @javax.enterprise.context.ApplicationScoped. Während der Laufzeit wird der CDI-Container die passende Bean anhand des Typen und des Kontexts suchen und in die Manufacturer Bean injizieren. Die Injizierung geschieht zur Laufzeit, wenn das Feld zum ersten Mal verwendet wird.

Listing 1.1: Injizierung einer Bean
public class Manufacturer {
  @Inject Builder carBuilder;
  public Car createCar() {
    return carBuilder.build();
  }
}
Listing 1.2: Zu injizierende Bean
@ApplicationScoped
public class Builder {
  public Car build() {
    return new Car();
  }
}
Erklärung zu @Inject

Die @javax.inject.Inject-Annotation kann für Felder, Methoden und Klassenkonstruktoren verwendet werden. Injizierte Felder dürfen dabei nicht final deklariert sein. Methoden dürfen nicht abstrakt sein und können mehrere Abhängigkeiten als Argumente besitzen. Pro Klasse darf maximal nur ein Konstruktor mit @javax.inject.Inject deklariert sein. Sollte eine Bean keinen mit @javax.inject.Inject annotierten Konstruktor besitzen, so wird per default der Default-Konstruktor zur Generierung der Bean verwendet.

Beispiel: Methodeninjizierung

In Listing 2 wird die Builder Bean direkt in die Methode injiziert, in der sie verwendet wird. Das macht Sinn, wenn das der einzige Ort in den Klasse ist, an dem die Bean verwendet wird.

Listing 2: Injizierung als Parameter im Methodenaufruf
public class Manufacturer {
@Inject
  public Car createCar( Builder builder ) {
    return builder.build();
  }
}
Erklärung zu Beans

Generell kann jede POJO-Klasse als Bean angesehen werden. Für CDI sind automatisch alle Objekte, die nicht mit dem new-Operator erzeugt werden, eine Bean. Bei Java-EE-Anwendungen verwaltet der CDI-Container alle Beans. Dieser ist dabei für die Erzeugung und den Lifecycle aller Beans verantwortlich. Sollte ein Kontext einer Bean beendet werden, so werden automatisch auch alle Beans dieses Kontexts zerstört. Jede Java-EE-Komponente ist eine Bean, wenn sie von der EE-Spezifikation als Managed Bean angesehen wird. Das beinhaltet Session Beans, Message-driven Beans, Servlets, Filters und viele mehr (Listing 3).

Listing 3: Verwaltung von Beans durch CDI
// wird nicht von CDI verwaltet
Car myCar = new Car();
  
// vom CDI Container verwaltet
@Inject Car myCar;    
Beispiel: Producer-Methode

Möchte der Entwickler die Erzeugung von Objektinstanzen kontrollieren, lassen sich hierzu Producer-Methoden verwenden. Mit @javax.enterprise.inject.Produces annotierte Methoden erzeugen neue Instanzen von Objekten. Alle Argumente von Producer-Methoden werden automatisch mit CDI injiziert. In Listing 4 wird in die SportsCarBuilder Bean eine Engine injiziert. Der CDI-Container findet automatisch die passende Producer-Methode und ruft diese auf, wenn eine Instanz einer Engine in der Anwendung benötigt wird. In diesem Beispiel wird auch eine Instanz für die Bean EngineParts erzeugt und an den Konstruktor der Engine Bean übergeben.

Listing 4.1: Producer-Methode
public class EngineProducer {
  @Produces
  public Engine createEngine( EngineParts parts ) {
    return new Engine( parts );
  }
}    
Listing 4.2: Injizierung der Bean
public class SportsCarBuilder implements Builder {
  @Inject Tires tires;
  @Inject Body body;
  @Inject Engine engine;
  public SportsCar build() {
    return new SportsCar( tires, body, engine );
  }
}  
Beispiel: Producer-Feld

Eine weitere Variante für Producer ist das Producer-Feld. Sein Wert ist der Wert, der injiziert wird. In Listing 5 ist zu sehen, wie zuerst die Methode createTire aufgerufen wird, wenn eine Instanz von Tires in der Anwendung benötigt wird.

Listing 5.1: Producer-Feld
public class TireProducer {
  @Produces Tires tire;
  
@Inject
  public void createTire( Rim rim ) {
    tire = new Tires( rim );
  }
}

Producer-Felder können auch mit Java-EE-Annotationen wie @Resource, @WebServiceRef, @EJB und @PersistenceContext verwendet werden. Das hat den Vorteil, dass CDI die EntityManager Bean verwaltet und diese typensicher in der Anwendung injiziert werden kann.

Listing 5.2: Producer-Feld mit EJB verbinden
@Produces @PersistenceContext
  EntityManager em;

Ein weiteres Beispiel für den Einsatz von Producer-Methoden ist die Erzeugung von Logger Beans. In dem folgenden Beispiel erzeugt die Producer-Methode createLogger einen Logger, der überall im Quellcode verwendet werden kann, wo eine LoggerBean injiziert wird (Listing 6). Um Metainformationen über die Klasse zu erhalten, in der die erzeugte Bean injiziert wird, verwendet man das InjectionPoint-Interface. In Listing 6 ist zu sehen, wie mithilfe des InjectionPoints der korrekte Name der Klasse für die Initialisierung der Logger Bean geholt wird. Gewöhnlich hätte ein Entwickler für jede Klasse explizit den Namen der Klasse eingetragen, z. B. LoggerFactory.getLogger( Manufacturer.class ).

Listing 6.1: Producer-Methode für Logging
public class LogFactory {
@Produces 
Logger createLogger( InjectionPoint ip ) { 
return LoggerFactory.getLogger(ip.getMember().getDeclaringClass().getName()); 
  }
}
Listing 6.2: Verwendung der Logger Bean
public class Manufacturer {
@Inject Logger logger;
...
logger.info("Logging using injected Logger Bean");
Kontexte

Wie Dependency Injection mit CDI funktioniert, hätten wir geklärt. Aber ein sehr wichtiger Teil von CDI betrifft die automatische Verwaltung von Kontexten. Alle Beans, die vom CDI-Container verwaltet werden, gehören zu einem bestimmten Kontext. Alle Kontexte haben einen vorgegebenen Lifecycle, der durch den Scope vorgegeben wird, dem sie zugewiesen sind. Standardmäßig kennt CDI fünf Scopes: Request Scope, Conversation Scope, Session Scope, Application Scope und Dependent Scope. Die Scopes in CDI können um neue erweitert werden, die wiederum mit der Annotation @javax.inject.Scope oder @javax.enterprise.inject.context.NormalScope versehen werden. CDI verwaltet seine kontextuellen Beans automatisch in den jeweiligen Kontexten. Wenn eine Bean injiziert wird, prüft der CDI-Container im zugehörigen Kontext, ob eine Bean existiert. Falls noch keine existiert, wird sie erzeugt und dem Kontext zugewiesen. Wenn ein Kontext zerstört wird, werden auch alle zugehörigen Beans zerstört.

Bean Scopes

Beans im @javax.enterprise.context.RequestScoped-Kontext leben für die Dauer eines Requests. Das kann ein HTTP Request, ein Remote EJB-Aufruf, ein Web-Service-Aufruf oder ein Nachrichtenaustausch mit einer Message-driven Bean sein. Nach dem Request werden diese Beans automatisch zerstört.
Beans im @javax.enterprise.context.ConversationScoped-Kontext werden über mehrere Requests hinweg genutzt, wenn sie zur selben HTTP-Session gehören. In der HTTP-Session muss auch eine aktive Conversation existieren. In JSF werden Conversations durch die javax.enterprise.context.Conversion Bean unterstützt.
Beans im @javax.enterprise.context.SessionScoped-Kontext werden über mehrere Requests hinweg genutzt, wenn sie zur selben HTTP-Session gehören. Wenn die Session zerstört wird, werden auch alle Beans zerstört.
Beans im @javax.enterprise.context.ApplicationScoped-Kontext leben so lange wie die Applikation. Nur wenn die Applikation beendet wird, werden auch diese Beans zerstört.
Wenn eine Bean keinen expliziten Scope besitzt, wird sie mit @javax.enterprise.context.Dependent annotiert. Diese Beans werden nicht von unterschiedlichen Injection-Punkten gemeinsam genutzt, sondern es wird jedes Mal eine neue Instanz der Bean erzeugt. Der Lebenszyklus der erzeugten Bean hängt danach an dem Lebenszyklus der Bean, in die sie injiziert wurde.

Auflösung der richtigen Beans

Die CDI-Spezifikation definiert einen Typesafe-Resolution-Algorithmus, der während der Systeminitialisierung ausgeführt wird, wenn alle Injection-Punkte mit den richtigen Beans injiziert werden müssen. Der Entwickler wird sofort bei der Initialisierung seiner Anwendung darauf hingewiesen, wenn eine Bean nicht aufgelöst werden kann oder nicht eindeutig entschieden werden kann, welche Bean verwendet werden soll. Damit mehrere Beans den gleichen Bean-Typen implementieren können, gibt es mehrere Möglichkeiten für den Entwickler, diese für die Laufzeit zu konfigurieren.
Zum einen können die Beans durch @javax.inject.Qualifier unterschieden werden. Qualifiers sind Annotationen für Bean-Typen und Injection-Punkte, die diese unterscheiden. Qualifiers sind somit zusätzlich Markierungen für Beans, um diese eindeutig zu machen.
Des Weiteren kann für ein spezielles Deployment eine Alternative aktiviert oder deaktiviert werden. Das geschieht durch das Annotieren einer Bean mit @javax.enterprise.inject.Alternative. Um diese Bean zu aktivieren, muss sie in der beans.xml explizit hinzugefügt werden.
Ist genau nur eine Bean für einen Typen vorhanden und es existiert ein Injection-Punkt mit genau diesem Typen, dann injiziert der CDI-Container auch genau diese eine Bean. Das ist der einfachste Fall. Andere Fälle, in denen Qualifier und Alternativen zum Einsatz kommen, werden jetzt anhand von Beispielen erklärt.

Beispiel: Qualifier

Qualifier können einer Klasse hinzugefügt werden, um einen qualifizierten Bean-Typen zu definieren, der dann wiederum der Dependency-Resolution-Algorithmus bei der Auflösung hilft. In den meisten Fällen werden diese Klassen Interfaces implementieren (implements) oder eine andere Klasse erweitern (extends). Eine Bean kann mehrere Qualifiers besitzen. Ein Injection-Punkt braucht hingegen nur so viele Qualifier wie notwendig, um die Beans eindeutig zuzuordnen. In dem folgenden Beispiel hat das sportsCarBuilder-Feld einen Qualifier @Sport und das familyCarBuilder-Feld zwei Qualifier, @New und @Family. Der Qualifier @New sorgt dafür, dass immer eine neue Instanz der Bean erzeugt wird.

Listing 7.1: Einsatz von Qualifiern
@ApplicationScoped
public class Builder {
  public Car build() {
    return new Car();
  }
}
Listing 7.2: Definition für den Qualifier „Sport“
@ApplicationScoped
public class Builder {
  public Car build() {
    return new Car();
  }
}
Listing 7.3: Deklarierung des speziellen Builders mit dem Qualifier „Sport“
@ApplicationScoped
public class Builder {
  public Car build() {
    return new Car();
  }
}

Qualifer können auch bei Producer-Methoden und Feldern verwendet werden, um die Bean-Typen zu unterscheiden. In Listing 8.1 annotiert der Qualifier @Family die Producer-Methode build.

Listing 8.1: Qualifier an einer Producer-Methode
@ApplicationScoped
public class Builder {
  public Car build() {
    return new Car();
  }
}

Qualifier können zudem auch noch Member enthalten. Diese können dazu verwendet werden, um nützliche Metainformationen an eine Methode zu übergeben. In Listing 8.2 wird noch eine Farbe der Producer-Methode mit übergeben. Der Qualifier @EngineType ist zusätzlich noch mit @Nonbinding annotiert, da das @Producer-Feld ansonsten nicht mit dem Injection-Punkt abgeglichen werden kann.

Listing 8.2: Definition für einen Qualifier mit einem Member
@ApplicationScoped
public class Builder {
  public Car build() {
    return new Car();
  }
}

Die nonbinding-Werte können verwendet werden, indem der InjectionPoint in der Producer-Methode injiziert und der Wert programmatisch geparsed wird (Listing 8.3).

Listing 8.3: Nonbinding-Wert eines Qualifiers programmatisch parsen
@ApplicationScoped
public class Builder {
  public Car build() {
    return new Car();
  }
}

Wo immer jetzt eine Engine injiziert wird, kann noch der @EngineType durch den neuen Qualifier mit angegeben werden.

Listing 8.4: Verwendung von einem Qualifier mit Member-Variablen
@ApplicationScoped
public class Builder {
  public Car build() {
    return new Car();
  }
}
Kommentare
  1. eduard2013-12-10 12:18:03

    Ab Listing 7.1 sind alle Listings gleich!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.