Context und Dependency Injection mit CDI

Next Generation Application Development: Fortgeschrittene Konzepte

Daniel Arndt und Marcel Birkner

Die ersten beiden Artikel dieser Serie beschäftigten sich mit den CDI-Grundlagen und dem Testen von Java-Enterprise-Applikationen. Dieser Artikel geht auf die fortgeschrittenen Konzepte ein, die das reichhaltige CDI-Programmiermodell bietet. Dabei vertiefen wir die Mechanismen zur typsicheren und losen Kopplung von Java-Komponenten.

Die Verwendung von CDI fördert ein besseres, objektorientiertes Design und unterstützt die Verwendung von bewährten Design Patterns [1]. Das wird besonders bei den nun vorgestellten Konzepten deutlich. Crosscutting Concerns aus Anwendungskomponenten werden mit CDI-Mechanismen wie Interceptors und Decorators auf einfache Art und Weise modularisiert. Danach zeigen wir Ihnen wie Anwendungskomponenten durch einen Eventmechanismus bereits zur Kompilierzeit entkoppelt werden können. Zum Schluss gehen wir auf Alternatives, Stereotypen und Spezialisierungen ein, die dem Entwickler weitere Mechanismen an die Hand geben, und den Sourcecode sauber, strukturiert und übersichtlich zu halten. Alle hier vorgestellten Beispiele finden sie als Maven-Projekte auf GitHub [2].

Interceptors

Mit der Java-EE-5-Version wurden bereits Interceptors eingeführt. Allerdings konnten diese Interceptors ausschließlich von EJB-Komponenten genutzt werden. CDI ermöglicht es nun, dass siee von allen Managed Beans verwendet und annotationsgetrieben, über die so genannten Interceptor Bindings, logisch gebunden und injiziert werden können. Interceptors bieten sich sehr gut an um orthogonale Zuständigkeiten innerhalb einer Applikation umzusetzen, da diese unabhängig von der Semantik des jeweiligen Aufrufkontexts sind. Damit eignen sich Interceptors für technische Crosscutting Concerns wie z. B. Transactions, Security und Logging.
In dem ersten Beispiel zeigen wir, welche Schritte notwendig sind, um einen CDI Interceptor zu implementieren. Das Interceptor Binding (Listing 1.1) ist eine Annotation, mit der der Interceptor und die zu injizierende Stelle deklariert werden. Um transaktionale Aufrufe an Typen oder Methoden zu ermöglichen, wird das Interceptor Binding @Transactional definiert. Der Interceptor implementiert dieses Verhalten in der Methode doTransaction (Listing 1.2). Der Interceptor kann jeden Methodenaufruf mittels @javax.interceptor.AroundInvoke abfangen und eine Transaktionsklammer, wenn nicht bereits eine aktive Transaktion vorhanden ist, mittels JTA öffnen und schließen bzw. im Fehlerfall ein Rollback initiieren. Die UserTransaction wird, wie bereits aus Java EE 5 bekannt, mittels @javax.annotation.Resource injiziert [4,5].

Listing 1.1: Transactional Interceptor Binding
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface Transactional {}
Listing 1.2: Interceptor Implementierung
@Interceptor @Transactional
public class TransactionalInterceptor {
      
      @Resource UserTransaction tx;

      @AroundInvoke
      public Object doTransaction(InvocationContext context) throws Exception {
             
             Object result = null;
             boolean isActiveTransaction = tx.getStatus() == Status.STATUS_ACTIVE;
             try {
                    if (!isActiveTransaction) {
                           tx.begin();
                    }
                    result = context.proceed();
                    if (!isActiveTransaction) {
                           tx.commit();
                    }       

             } catch (Exception e) {
                    tx.rollback();
                    throw e;
             }
             return result;
      }
}

In dem weiteren Beispiel aus Listing 2.1 wird ein PerformanceInterceptor hinzugefügt, der die Laufzeit eines Methodenaufrufs misst. Der PerformanceInterceptor wird immer vor dem TransactionalInterceptor aufgerufen. Im PerformanceInterceptor wird der Logger injiziert, auf den bereits im ersten Artikel der Reihe eingegangen wurde. Dieser Logger wird mittels @javax.enterprise.inject.Produces von der LogFactory Bean erzeugt.

In Listing 2.2 wird über das Interceptor Binding @Performance der PerformanceInterceptor an jede Methode der Bean MoneyTransferService gebunden. Der TransactionalInterceptor wird mit @Transactional dediziert an die Methode transfer gebunden. Um einen Interceptor zu aktivieren, muss er in der beans.xml eingetragen werden. Die Aufrufreihenfolge der Interceptors während der Laufzeit entspricht der Reihenfolge in der beans.xml (Listing 2.3).

Listing 2.1: Performance Interceptor
@Interceptor @Performance
public class PerformanceInterceptor {

      @Inject private Logger logger;

      @AroundInvoke
      public Object doPerformanceMeasurement(InvocationContext context)
                    throws Exception {

             Object result = null;
             long start = System.currentTimeMillis();
             result = context.proceed();
             if(logger.isDebugEnabled()) {
                    logger.debug(context.getMethod() + " done in "
                                  + (System.currentTimeMillis() - start) + " ms");
             }
             return result;
       }
}
Listing 2.2: Verwendung von Interceptors @Performance und @Transactional
@Performance
public class MoneyTransferServiceBean implements MoneyTransferService {       
      ...
      @Transactional
      public void transfer(Account from, Account to, Money amount) {
             ...              
      }

      public List getHistory(Account account) {
             ...              
      }
}
Listing 2.3: Interceptor Aktivierung in der beans.xml
de.sample.PerformanceInterceptorde.sample.TransactionalInterceptor
Geschrieben von
Daniel Arndt und Marcel Birkner
Kommentare

Schreibe einen Kommentar

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