Wenn man mit JavaEE (oder JakartaEE, wie es heute heißt) arbeitet, begegnet man früher oder später einem ziemlich mächtigen, aber oft übersehenen Konzept: den Interceptoren. Interceptoren sind kleine Klassen, die sich zwischen den Aufruf eines Methodenaufrufs und dessen tatsächliche Ausführung schalten. Sie können also Code ausführen, bevor oder nachdem eine Methode läuft – ohne, dass man diesen Code in jeder einzelnen Methode wiederholen muss.
Man kann sie sich vorstellen wie einen Türsteher, der jeden Besucher (also jeden Methodenaufruf) kurz abfängt, prüft, und dann entscheidet, ob und wie er hinein darf. Praktisch ist das zum Beispiel für Logging, Transaktionen, Performance-Messungen oder Sicherheitsprüfungen.
Wir beginnen mit einem ganz einfachen Beispiel, um zu zeigen, wie ein Interceptor funktioniert. Zuerst brauchen wir eine Annotation, mit der wir markieren, wo der Interceptor greifen soll.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.interceptor.InterceptorBinding;
@InterceptorBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Logged {}
Mit dieser Annotation @Logged können wir später angeben, dass eine Methode oder eine ganze Klasse protokolliert werden soll. Jetzt erstellen wir den eigentlichen Interceptor.
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
@Logged
@Interceptor
public class LoggingInterceptor {
@AroundInvoke
public Object logMethodCall(InvocationContext context) throws Exception {
System.out.println("Starte Methode: " + context.getMethod().getName());
Object result = context.proceed();
System.out.println("Beende Methode: " + context.getMethod().getName());
return result;
}
}
Das ist der ganze Zauber: In der Methode logMethodCall rufen wir zuerst unseren eigenen Code auf – hier zwei einfache Log-Ausgaben – und danach context.proceed(), um die eigentliche Zielmethode auszuführen. Danach können wir wieder etwas tun, z.B. den Abschluss loggen.
Jetzt wenden wir unseren Interceptor auf eine Klasse an, etwa auf einen simplen Service:
import jakarta.enterprise.context.RequestScoped;
@RequestScoped
@Logged
public class GreetingService {
public String sayHello(String name) {
return "Hallo " + name + "!";
}
}
Und schließlich rufen wir den Service auf, z.B. über eine einfache CDI-bean-getriebene Anwendung:
import jakarta.inject.Inject;
public class MainApp {
@Inject
private static GreetingService greetingService;
public static void main(String[] args) {
GreetingService service = new GreetingService();
System.out.println(service.sayHello("Marcy"));
}
}
Wenn man das Programm startet (in einem echten JavaEE-Container wie Wildfly natürlich mit aktivem CDI- und Interceptor-Support), sieht die Ausgabe etwa so aus:
Starte Methode: sayHello Beende Methode: sayHello Hallo Marcy!
Wie man sieht, wurde der Methodenaufruf von unserem Interceptor „abgefangen“ und erweitert – ohne, dass wir im Code der Methode selbst etwas ändern mussten. Das ist das eigentliche Ziel von Interceptoren: Querschnittsfunktionen zentral und wiederverwendbar zu implementieren.
Ein weiteres klassisches Beispiel ist die Messung der Ausführungszeit. Mit einem Interceptor lässt sich das in wenigen Zeilen lösen:
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
@Performance
@Interceptor
public class PerformanceInterceptor {
@AroundInvoke
public Object measureTime(InvocationContext context) throws Exception {
long start = System.currentTimeMillis();
Object result = context.proceed();
long duration = System.currentTimeMillis() - start;
System.out.println("Methode " + context.getMethod().getName() + " dauerte " + duration + "ms");
return result;
}
}
Die passende Annotation dazu:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.interceptor.InterceptorBinding;
@InterceptorBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Performance {}
Und hier wieder die Anwendung:
@Performance
public class CalculationService {
public long heavyCalculation() {
long sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
}
}
Beim Aufruf dieser Methode würde im Log stehen:
Methode heavyCalculation dauerte 12ms
Auch hier gilt wieder: Der Code der Methode selbst bleibt sauber und frei von zusätzlicher Logik.
Ein Interceptor ist im Prinzip eine Art „Middleware“ im JavaEE-Kontext. Er kann Dinge tun, bevor und nachdem eine Methode ausgeführt wird. Besonders stark wird das Konzept, wenn man mehrere Interceptoren kombiniert oder sie gezielt nur auf bestimmte Klassen oder Methoden anwendet.
Wichtig ist: Interceptoren greifen nur, wenn sie im Container-Umfeld laufen – also in einer echten CDI- oder EJB-Umgebung. In einem einfachen main-Programm ohne Container funktioniert das nicht, weil der Container dafür sorgt, dass die Aufrufe über den Interceptor laufen.
In der Praxis verwendet man Interceptoren häufig für:
• Logging: Zentrale Ausgabe aller Methodenaufrufe ohne redundanten Code.
• Performance-Messung: Zeitmessung und Auswertung über alle Schichten hinweg.
• Security: Prüfung von Benutzerrechten, bevor eine Methode ausgeführt wird.
• Transaktionssteuerung: Sicherstellen, dass bestimmte Methoden innerhalb einer Transaktion laufen.
Das Schöne daran: Der Entwickler der eigentlichen Fachlogik (Business Code) muss sich um diese Themen gar nicht kümmern. Der Interceptor kapselt sie zentral, wodurch der Code wartbarer, testbarer und übersichtlicher bleibt.
Interceptoren sind eines dieser Werkzeuge, die man am Anfang vielleicht übersieht, später aber nicht mehr missen möchte. Sie helfen, Code sauber zu halten, Querschnittsfunktionen zentral zu bündeln und Projekte strukturiert zu gestalten. Gerade als Einsteiger sollte man sich angewöhnen, frühzeitig über solche Konzepte nachzudenken – sie machen aus „funktionierendem“ Code langfristig „professionellen“ Code.
Wenn du also das nächste Mal überlegst, in jeder Methode ein System.out.println() für Logging einzubauen – halte kurz inne. Vielleicht ist ein Interceptor genau das, was du brauchst.
