Next Generation Application Development: Fortgeschrittene Konzepte

Decorators

Im Gegensatz zu Interceptors haben Decorators eine klare Abhängigkeit zu ihrem Aufrufkontext. Decorators lassen sich nicht auf jeden Typen anwenden und beziehen sich mehr auf dedizierte Typen und deren Semantik. Der Decorator muß das Interface der Bean implementieren, die dekoriert werden sollen, in diesem Fall das MoneyTransferService Interface (Listing 3.1). In diesem Beispiel soll der MoneyTransferService mit einer Überprüfung des Überweisungslimits für ein zu belastendes Konto dekoriert werden. Die Implementierung des Decorators DebtLimitVerification wird mit @javax.decorator.Decorator annotiert und verfügt über einen Delegate Injection Point, der mit @javax.decorator.Delegate annotiert ist und den gleichen Typ wie der Decorator besitzt (Listing 3.2). Der Decorator wird an alle Beans gebunden, die das Interface MoneyTransferService implementieren. Wie bei Interceptors müssen Decorators in der beans.xml eingetragen werden, damit sie aktiviert werden (Listing 3.3).

Listing 3.1: Interface Beschreibung
public interface MoneyTransferService {
public void transfer(Account from, Account to, Money amount);
public List getHistory(Account accout);
}
Listing 3.2: Decorator
@Decorator
public abstract class DebtLimitVerification implements MoneyTransferService {
      
      @Inject DebtLimitService debtLimitService;
      
      @Inject @Delegate @Any MoneyTransferService delegate;

      @Override
      public void transfer(Account from, Account to, Money amount) {
             if(debtLimitService.isExceeded(from, amount)) {
                    throw new DebtLimitExceeded(from, amount);
             }
             delegate.transfer(from, to, amount);
      }
}
Listing 3.3: beans.xml mit aktiviertem Decorator
de.sample.DebtLimitVerification

In Listing 3.2 ist der Delegate Injection Point mit @javax.enterprise.inject.Any annotiert. In manchen Szenarien kann es sinnvoll sein, die zu dekorierenden Beans anhand ihrer Qualifier einzuschränken. Hierzu müßte der Delegate Injection Point mit einem entsprechenden Qualifier annotiert werden. In diesem Beispiel würden nur Auslandsüberweisungen dekoriert werden.


@Inject @Delegate @ForeignTransfer MoneyTransferService delegate;

Events

CDI stellt mit Events einen weiteren Mechanismus zur Verfügung, der einen noch höheren Grad an Entkopplung bietet und keiner Abhängigkeit zur Kompilierzeit bedarf. CDI ermöglicht es, mittels des Observer Patterns, Nachrichten über Events zwischen so genannten Event Producern und Observer-Methoden auszutauschen. Im Vergleich zum klassischen Observer Pattern bietet die CDI-Variante folgende Vorteile:

  • Event Producer, als auch Observer-Methoden, sind vollständig voneinander entkoppelt.
  • Es besteht die Möglichkeit, mit der Hilfe von @javax.inject.Qualifier den Nachrichtenaustausch auf bestimmte Events einzuschränken.
  • Die Benachrichtigung der Observer-Methoden kann an einen Transaktionskontext gebunden werden und erfolgt z. B. erst bei erfolgreicher Durchführung eines Commits.

Zum Nachrichtenaustausch zwischen Event Producern und Observer-Methoden werden POJOs eingesetzt. In dem Beispiel aus Listing 4.1 wird eine Java-Klasse als Nachricht (AuditEvent) für eine Audit-Komponente fungieren. Es wird ein AuditEvent ausgelöst, wenn das Überweisungslimit (DebtLimitExceededEvent) für ein Konto im bereits bekannten DebtLimitVerifaction Decorator überschritten wird (Listing 4.2).

Um Events erzeugen zu können, wird ein Event Producer benötigt (Listing 4.3). Dieser wird als eine mit der Event-Klasse AuditEvent parametrisierte Instanz des javax.enterprise.event.Event Interfaces von CDI injiziert. Über diesen Event Producer ist es möglich jegliche Ausprägungen der AuditEvent-Klasse zu versenden, da er mit @javax.enterprise.inject.Any annotiert ist.

Das Auslösen des DebtLimitExceededEvent erfolgt über die Methode fire des Event Producers (Listing 4.4). Um einen Observer für dieses Event zu implementieren wird eine Methode benötigt, die AuditEvent als Parameter besitzt und mit @javax.enterprise.event.Observes annotiert ist (Listing 4.5).

Listing 4.1: Audit Event
public class AuditEvent {
      
      private Account account;
      
      public AuditEvent(Account account) {
             this.account = account;
      }
      
      public Account getAccount() {
             return account;
      }
}
Listing 4.2: Spezielle Audit Event Implementierung
public class DebtLimitExceededEvent extends AuditEvent {

      public DebtLimitExceededEvent(Account account) {
             super(account);
      }
}
Listing 4.3: Event Producer
@Inject @Any Event auditEventProducer;
Listing 4.4: Auslösen des AuditEvents
public void transfer(Account from, Account to, Money amount) {
    if(debtLimitService.isExceeded(from, amount)) {
        auditEventProducer.fire(new DebtLimitExceededEvent(from));
             throw new DebtLimitExceeded(from, amount);
    }
    delegate.transfer(from, to, amount);
}
Listing 4.5: Implementierung eines Observers für AuditEvents
private void onAuditEvents(@Observes AuditEvent event) {
      ...
 }
      

Wichtig für das Verständnis der CDI-Events ist es, dass die Ausführung der Methoden transfer und onAuditEvents synchron erfolgt. Aus diesem Grund muss vorsichtig mit zeitintensiven Verarbeitungen in Observer-Methoden umgegangen werden. Momentan bietet CDI keine Möglichkeit einer asynchronen Verarbeitung in Observer-Methoden an. Mittels JMS oder über die mit EJB 3.1 eingeführte Annotation @javax.ejb.Asynchronous kann man sich allerdings behelfen [6].

Die Methode onAuditEvents verarbeitet ohne Einschränkung alle Events vom Typ AuditEvent. Jedoch kann es sinnvoll sein dieses Verhalten weiter einzuschränken. Über einen neuen Qualifier @AuditViolation, der am Injection Point des Event Producers annotiert wird, werden nur noch Events dieses Qualifier versendet.

@Inject @AuditViolation Event auditEventProducer;

Die Einschränkung über Qualifier kann wie ein Topic Selector genutzt werden. Nachteilig ist, dass durch einen Qualifier am Injection Point des Event Producers alle Events nur ausschließlich mit diesem Qualifier versendet werden können und dieser nicht dynamisch beeinflußt werden kann. Alternativ kann ein Qualifier über die Klasse javax.enterprise.util.AnnotationLiteral mittels der Methode select zur Laufzeit gesetzt werden.

auditEventProducer
      .select(new AnnotationLiteral() {})
      .fire(new DebtLimitExceededEvent(from));

Die passende Observer-Methode wird auch mit @AuditViolation annotiert und verarbeitet nur Events, die mit dieser Annotation versendet werden.

@Decorator
public abstract class DebtLimitVerification implements MoneyTransferService {
      
      @Inject DebtLimitService debtLimitService;
      
      @Inject @Delegate @Any MoneyTransferService delegate;

      @Override
      public void transfer(Account from, Account to, Money amount) {
             if(debtLimitService.isExceeded(from, amount)) {
                    throw new DebtLimitExceeded(from, amount);
             }
             delegate.transfer(from, to, amount);
      }
}

CDI-Events können auch mit mehreren Qualifiers versehen werden. Der Mechanismus zur Benachrichtigung bei einem überzogenen Überweisungslimit kann z. B. über eine allgemeine ApplicationEvent-Klasse mit dem Qualifier @Audit erfolgen. Der Qualifier @AuditViolation wird nach wie vor dynamisch gesetzt (Listing 5.1 und 5.2).

Listing 5.1: Qualifier dynamisch setzen
@Decorator
public abstract class DebtLimitVerification implements MoneyTransferService {
      
      @Inject DebtLimitService debtLimitService;
      
      @Inject @Delegate @Any MoneyTransferService delegate;

      @Override
      public void transfer(Account from, Account to, Money amount) {
             if(debtLimitService.isExceeded(from, amount)) {
                    throw new DebtLimitExceeded(from, amount);
             }
             delegate.transfer(from, to, amount);
      }
}
Listing 5.2: Observer Methode mit @Audit Annotation
@Decorator
public abstract class DebtLimitVerification implements MoneyTransferService {
      
      @Inject DebtLimitService debtLimitService;
      
      @Inject @Delegate @Any MoneyTransferService delegate;

      @Override
      public void transfer(Account from, Account to, Money amount) {
             if(debtLimitService.isExceeded(from, amount)) {
                    throw new DebtLimitExceeded(from, amount);
             }
             delegate.transfer(from, to, amount);
      }
}

Ein weiteres interessantes Feature ist die Bindung von Observer-Methoden an ein transaktionales Verhalten. Wie bereits erwähnt, findet die Ausführung der transfer-Methode und der jeweiligen Observer-Methoden synchron statt. Auch beim Thema Transaktionen findet die Verarbeitung im gleichen Transaktionskontext der Methode transfer statt. Standardmäßig wird das Event umgehend der Observer-Methode zugestellt. Über die Angabe der Transaktionsphase kann dieser Zusammenhang gesteuert werden und z. B. der Event erst bei einem erfolgreichen Commit zugestellt werden. Die Methode transfer des MoneyTransferService wird um einen weiteren AuditEvent (MoneyTransferDone) erweitert. Dieser AuditEvent zeigt an, dass eine Überweisung erfolgreich durchgeführt wurde.

@Decorator
public abstract class DebtLimitVerification implements MoneyTransferService {
      
      @Inject DebtLimitService debtLimitService;
      
      @Inject @Delegate @Any MoneyTransferService delegate;

      @Override
      public void transfer(Account from, Account to, Money amount) {
             if(debtLimitService.isExceeded(from, amount)) {
                    throw new DebtLimitExceeded(from, amount);
             }
             delegate.transfer(from, to, amount);
      }
}

Da die Methode transfer durch den TransactionalInterceptor in einer Transaktionsklammer abläuft und MoneyTransferDone erst nach einem erfolgreichen Commit zugestellt werden soll, parametrisieren wir die @javax.enterprise.event.Observes-Annotation der Observer-Methode onMoneyTranferDoneEvent mit during=TransactionPhase.AFTER_SUCCESS.

@Decorator
public abstract class DebtLimitVerification implements MoneyTransferService {
      
      @Inject DebtLimitService debtLimitService;
      
      @Inject @Delegate @Any MoneyTransferService delegate;

      @Override
      public void transfer(Account from, Account to, Money amount) {
             if(debtLimitService.isExceeded(from, amount)) {
                    throw new DebtLimitExceeded(from, amount);
             }
             delegate.transfer(from, to, amount);
      }
}

Tabelle 1 zeigt alle Transaktionsphasen auf die in der CDI Eventverarbeitung reagiert werden kann.

Tabelle 1: Transaktionsphasen für CDI Eventverarbeitung

Transaktionsphasen Beschreibung

IN_PROGRESS

Das Event wird dem Observer umgehend zugestellt (Standardverhalten)

AFTER_SUCCESS

Das Event wird dem Observer während der After Completion-Phase zugestellt, wenn die Transaktion erfolgreich abgeschlossen werden konnte

AFTER_FAILURE

Das Event wird dem Observer während der After Completion-Phase zugestellt, wenn die Transaktion nicht erfolgreich abgeschlossen werden konnte

AFTER_COMPLETION

Das Event wird dem Observer während der After Completion-Phase zugestellt

BEFORE_COMPLETION

Das Event wird dem Observer während der Before Completion-Phase zugestellt

Kommentare

Schreibe einen Kommentar

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