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).
Ein Interceptor muss an eine Zielklasse gebunden werden, damit der Container ihn tatsaechlich einreiht. Dieses Projekt zeigt beide moeglichen Wege nebeneinander:
Annotationsbasiert (PolicyManagementBean):
java
@Stateless
@Interceptors(AuditInterceptor.class)
public class PolicyManagementBean implements ... { ... }
Direkt im Code sichtbar, aber jede Aenderung braucht ein Neu-Deployment.
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.
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.
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.