Nachdem im ersten Teil der Blogserie „Freundliche Eindringlinge: CDI seams to let EJB spring (JSR 299)“ ein wenig Theorie zu CDI vermittelt und auch schon die Dependency Injection (DI) mit den Werkzeugen der Referenzimplementierung Weld aus dem Hause JBoss vorgestellt wurde, sollen in diesem Artikel die Werkzeuge Qualifier, Alternativen, Stereotypen, EL-Namen und Scopes vorgestellt werden.
Qualifier
Qualifier sind dazu da, bei mehreren Implementierungen eines Typs genau zu definieren, welche konkrete Implementierung von CDI injiziert werden soll. Dazu muss zunächst ein entsprechender Qualifier definiert werden bzw. eine mit @Qualifier annotierte Annotation.
@Qualifier @Target({TYPE, METHOD, PARAMETER, FIELD}) @Retention(RUNTIME) public @interface Holisticon {}
Die für Annotationen typischen Meta-Annotationen @Target und @Retention legen fest, was mit der definierten Annotation annotiert werden darf und wann die Auflösung der Annotation erfolgt.
Eine Stateless Session Bean wird mit diesem Qualifier annotiert.
@Holisticon @Stateless public class HolisticonBlogServiceBean implements BlogService { ... }
Und um auch die gewünschte Bean zu injizieren, wird der genutzte BlogService
ebenfalls mit dem Qualifier annotiert.
public class InvasionController { @Inject @Holisticon private BlogService blogService; ... }
Würde bei mehreren Implementierungen des BlogServices
auf einen Qualifier verzichtet werden, so würde Weld den Start des Containers verweigern und folgende Exception werfen:
WELD-001409 Ambiguous dependencies for type [BlogService] with qualifiers [@Default] at injection point ...
Der in der Exception erwähnte Qualifier @Default wird implizit gesetzt, wenn die Bean nicht explizit einen Qualifier bekommen hat.
Alternativen
Mittels Alternativen kann zur Deployment-Zeit festgelegt werden, dass anstatt der eigentlichen Implementierung eines Typs eine alternative Implementierung genutzt wird. Diese Alternative hat den Qualifier @Alternative und muss im CDI Deployment-Deskriptor, der beans.xml, aktiviert werden.
Die alternative Implementierung von BlogService
soll ein Mock sein.
@Alternative public class BlogServiceMock implements BlogService{ ... }
In der beans.xml wird die Alternative wie folgt aktiviert.
<alternatives> <class> de.holisticon.blog.fiendlyInvaders.service.mock.BlogServiceMock </class> </alternatives>
Wie das Codebeispiel suggerieren soll, sind diese Alternativen besonders nützlich, wenn es um das Testen einer Applikation geht.
Stereotypen
Sterotypen können als Annotationen verstanden werden, die mehrere Annotationen bündeln können.
Es kann z.B. ein Stereotyp angelegt werden, der die Annotationen @SessionScoped und @Named bündelt (die Annotationen werden im Folgenden noch erklärt). Ein Stereotyp wird mit der Annotation @Stereotype annotiert.
@Stereotype @Named @SessionScoped @Target(TYPE) public @interface NSS {}
Die mit @NSS annotierte Klasse bekommt also implizit @Named und @SessionScoped annotiert.
@NSS public class InvasionController implements Serializable {...}
EL-Namen
Die einfachste Variante, eine Bean dem CDI-Kontext hinzuzufügen, ist, sie mit @Named zu annotieren. Diese Annotation ermöglicht, dass eine Bean direkt in der Präsentationsschicht, wie z.B. einer JSF-Seite, angesprochen werden kann.
Zunächst wird ein Controller mit @Named annotiert.
@Named public class InvasionController{ @Inject @Holisticon private BlogService blogService; ... }
Ist eine Bean mit @Named annotiert, so ist sie mit ihrem Namen in der EL ansprechbar. Zu beachten ist, dass der Name hierbei immer mit einem kleinen Buchstaben beginnt. Der EL-Name der annotierten Bean ist hier invasionController
, wie im folgenden Auszug einer JSF Seite.
<ui:repeat var="blogArticle" value="#{invasionController.blogArticles}"> #{blogArticle.title} </ui:repeat>
Gefällt der automatische EL-Name der Beans nicht, so hat man die Möglichkeit, der Bean auch einen Namen via Annotation zuzuweisen (z.B. @Named („MeinName“)).
Scopes
Auch bei CDI gibt es Scopes, die die Lebenszeit von Objekten und das Verhalten von Objekten zueinander beschreiben. Beans können in CDI mit einem der folgenden Scopes annotiert werden:
- @RequestScoped
- @ConversationScoped
- @SessionScoped
- @ApplicationScoped
Wird eine Bean mit einem Scope annotiert, so wird sie dem entsprechenden Context im CDI Container zugewiesen.
@Named @SessionScoped public class InvasionController implements Serializable { private static final long serialVersionUID = 1L; @Inject @Holisticon private BlogService blogService; ... }
Dieser Controller wird an den SessionContext gebunden und bleibt für die Lebenszeit der Session erhalten.
An dieser Stelle ist gut zu wissen, dass Beans mit dem Session oder Conversation Scope serialisierbar sein müssen. Implementieren diese Beans nicht die Klasse Serializable
, so verweigert CDI den Start und wirft die folgende Exception:
WELD-000072 Managed bean declaring a passivating scope must be passivation capable.
Conversation Scope
Der einzig wirklich neue Scope, den CDI bietet, ist der Conversation Scope. Er schließt die Lücke zwischen dem Scope eines Requests und dem Scope einer Session, den Entwickler schon lange vermissten und teilweise durch eigene Implementierungen realisieren mussten. Die Vorteile der Conversation liegen auf der Hand, nutzerspezifische Daten können über mehrere Requests hinweg „gemerkt“ werden und es bleiben keine alten beziehungsweise nicht mehr benötigten Daten z.B. in der Session, da der Beginn und das Ende einer Conversation explizit gesetzt werden können. Ein weiteres schönes Feature ist, dass in jedem Browser Tab eine eigene Conversation gehalten wird.
In der Praxis ist der Conversation Scope unter anderem für einen Checkout-Prozess sehr nützlich, so auch im Codebeispiel. Dazu wird eine Stateful CheckoutServiceBean
mit der Annotation @ConversationScoped annotiert.
@Stateful @ConversationScoped public class CheckOutServiceBean { @Inject private Conversation conversation; private KudosCart kudosCart; public KudosCart createKudosCart() { kudosCart = new KudosCart(); // if no conversation is "open“ if (conversation.isTransient()) conversation.begin(); return kudosCart; } public void checkout() { kudosCart.getCart().clear(); conversation.end(); } }
Um die Conversation über mehrere Requests hinweg leben zu lassen – in der Spec wird von einer lange laufenden Conversation gesprochen – injizieren wir uns die Conversation in unsere Bean. Mit diesem Conversation-Objekt kann explizit festgelegt werden, wann eine Conversation beginnt (conversation.begin()
) und wann sie endet (conversation.end()
). Ob für einen Nutzer bereits eine Conversatiuon „geöffnet“ wurde, kann mit conversation.isTransient()
geprüft werden.
Zu beachten ist, dass die Conversation automatisch nur bei JSF Requests zur Verfügung steht. Agiert man nicht mit JSF Requests (Links, JavaScript etc.), muss eine Conversation ID (cid
) als Parameter mitgegeben werden.
<a href="/add.jsp?cid=#{ javax.enterprise.context.conversation.id }"> hinzufügen </a>
Ausblick
In diesem Blogartikel wurden die Werkzeuge Qualifier, Alternativen, Stereotypen, EL-Namen und Scopes vorgestellt. Um die Werkzeugkiste von CDI bzw. der JBoss-Referenzimplementierung Weld zu vervollständigen, werden im dritten und letzten Teil der Blogserie Events, Interzeptor-Bindings, Dekoratoren und Produzenten vorgestellt.
Alle Konzepte in Summe können zusätzlich anhand der Beispielapplikation „Friendly Invaders“ nachvollzogen werden, auf welche im dritten Artikel noch im Detail eingegangen wird.