← Zur Uebersicht

Interceptors

Beispiel: AuditInterceptor, gebunden an PolicyManagementBean und PremiumCalculationBean.

Ein Interceptor umschliesst einen Business-Methodenaufruf mit eigenem Code, ohne dass die Zielmethode selbst davon weiss - das klassische Werkzeug fuer Cross-Cutting-Concerns wie Logging, Auditing, Metriken oder generische Validierung, die sonst in jeder einzelnen Business-Methode wiederholt werden muessten.

@AroundInvoke

@AroundInvoke
public Object audit(InvocationContext context) throws Exception {
    Object result = context.proceed();   // ruft die eigentliche Zielmethode auf
    // ... vorher/nachher-Logik ...
    return result;
}

InvocationContext gibt Zugriff auf alles, was man fuer generisches Cross-Cutting-Verhalten braucht: getMethod() (welche Methode wird aufgerufen), getParameters() (mit welchen Argumenten), getTarget() (auf welcher Bean-Instanz) - und vor allem proceed(), der Aufruf, der die Kette fortsetzt (entweder zum naechsten Interceptor oder, wenn keiner mehr uebrig ist, zur eigentlichen Zielmethode). Ruft ein Interceptor proceed() nicht auf, wird die Zielmethode gar nicht erst ausgefuehrt - ein maechtiges, aber auch gefaehrliches Werkzeug (z.B. fuer Zugriffskontrolle, die einen Aufruf ganz blockieren soll).

Zwei Bindungsarten - bewusst an verschiedenen Beans gezeigt

Ein Interceptor muss an eine Zielklasse gebunden werden, damit der Container ihn tatsaechlich einreiht. Dieses Projekt zeigt beide moeglichen Wege nebeneinander:

  1. Annotationsbasiert (PolicyManagementBean): java @Stateless @Interceptors(AuditInterceptor.class) public class PolicyManagementBean implements ... { ... } Direkt im Code sichtbar, aber jede Aenderung braucht ein Neu-Deployment.

  2. XML-basiert (PremiumCalculationBean, ueber META-INF/ejb-jar.xml): xml <interceptor-binding> <ejb-name>PremiumCalculationBean</ejb-name> <interceptor-class>com.novaris.legacy.policy.interceptor.AuditInterceptor</interceptor-class> </interceptor-binding> Die Zielklasse selbst traegt keine Interceptor-Annotation - die Bindung lebt ausschliesslich in der Konfigurationsdatei. Das erlaubt, Interceptor-Bindungen zwischen Umgebungen zu variieren (z.B. Auditing in Produktion aktiv, in einer Lasttest-Umgebung bewusst deaktiviert), ohne den Quellcode anzufassen oder neu zu kompilieren.

Beide Wege sind zur Laufzeit funktional gleichwertig - derselbe AuditInterceptor, dieselbe @AroundInvoke-Methode, nur unterschiedlich gebunden.

Interceptoren duerfen selbst Dependency Injection nutzen

AuditInterceptor injiziert per @Resource(lookup = ...) eine DataSource und baut sich in @PostConstruct seinen eigenen AuditLogDao. Das funktioniert, weil der Container fuer jede Zielinstanz eine eigene Interceptor-Instanz mit eigenem Lebenszyklus erzeugt - Interceptoren sind selbst container-verwaltete Objekte, keine simplen Utility-Klassen.

Design-Entscheidung: Auditfehler duerfen die Fachtransaktion nicht stoppen

AuditLogDao.write(...) faengt SQLException selbst ab und loggt nur (siehe dessen Javadoc) - ein Ausfall der Audit-Tabelle darf keine produktive Geschaeftsoperation verhindern. Diese Entscheidung sitzt bewusst innerhalb des DAO, nicht im Interceptor selbst - so bleibt der Interceptor-Code (audit(...)) einfach lesbar, und die "was, wenn das Audit-Log ausfaellt"-Entscheidung ist an genau einer Stelle zentral getroffen.

⌂ Cockpit