`@EventListener` in Spring Boot: So baust du lose gekoppelte Komponenten

Julian | Oct 20, 2025 min read

Hallo zusammen,

in modernen, auf Microservices ausgerichteten Architekturen wollen wir Komponenten so unabhängig wie möglich halten. Wenn Service A eine Aktion ausführt (z.B. einen Benutzer registriert), soll Service B darauf reagieren (z.B. eine Willkommens-E-Mail senden), ohne dass A direkt von B wissen muss.

Genau hier glänzt das Observer-Pattern, umgesetzt durch ApplicationEvent und die Annotation @EventListener in Spring Boot. Dieses Muster ist extrem mächtig und ermöglicht es uns, lose gekoppelte, hoch-kohäsive Komponenten zu bauen.


Das Prinzip: Entkopplung durch Events

Der Kern des Spring Event-Mechanismus ist die vollständige Entkopplung:

  1. Der Producer (Quelle): Er führt eine Aktion aus und veröffentlicht ein Event. Er weiß nicht, wer zuhört.
  2. Der Consumer (Listener): Er abonniert bestimmte Event-Typen und führt daraufhin seine eigene Logik aus. Er weiß nicht, wer das Event veröffentlicht hat.
  3. Der Broker (ApplicationEventPublisher): Spring Boot übernimmt die Rolle des Vermittlers.

Schritt 1: Das Event definieren

Zuerst definieren wir ein einfaches Event-Objekt. Es sollte alle Informationen enthalten, die der Listener benötigt, um seine Aufgabe zu erfüllen. In modernen Java-Versionen (Java 17+) ist ein record dafür ideal.

// src/main/java/com/example/events/UserRegisteredEvent.java

/**
 * Event, das ausgeloest wird, wenn ein neuer Benutzer registriert wurde.
 * Nutzt ein Java Record fuer eine immutable Datenstruktur.
 */
public record UserRegisteredEvent(
    String userId,
    String email,
    long timestamp
) {
    // Statische Factory-Methode (optional, aber guter Stil)
    public static UserRegisteredEvent create(String userId, String email) {
        return new UserRegisteredEvent(userId, email, System.currentTimeMillis());
    }
}

Schritt 2: Das Event veröffentlichen (Der Producer)

Um das Event zu veröffentlichen, benötigen wir den ApplicationEventPublisher, den wir uns einfach in unseren Service injizieren lassen.

// src/main/java/com/example/service/RegistrationService.java

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class RegistrationService {

    private final ApplicationEventPublisher eventPublisher;

    // Spring injiziert den Publisher automatisch
    public RegistrationService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void registerNewUser(String username, String email) {
        // 1. Kern-Logik ausfuehren (z.B. Speichern in der DB)
        String newUserId = generateAndSaveUser(username, email);
        
        // 2. Event erstellen
        UserRegisteredEvent event = UserRegisteredEvent.create(newUserId, email);
        
        // 3. Event publizieren – die Registrierung ist erfolgreich abgeschlossen!
        eventPublisher.publishEvent(event);
        
        System.out.println("User " + username + " erfolgreich registriert und Event publiziert.");
    }
    
    private String generateAndSaveUser(String username, String email) {
        // ... (Simulierte Speicherung)
        return "UUID-789-" + username;
    }
}

Der RegistrationService kümmert sich nur um unseren Job (Registrierung) und hat keine Ahnung, ob eine E-Mail versendet, ein Log-Eintrag erstellt oder ein Statistik-Zähler erhöht wird. Das ist lose Kopplung in Reinkultur!


Schritt 3: Das Event konsumieren (Der Listener)

Der Listener ist eine ganz normale Spring Component, die einfach die Annotation @EventListener über eine Methode setzt. Die Methode muss den Typ des Events, das sie verarbeiten soll, als Argument erwarten.

a) Synchroner Listener (Standard)

Standardmäßig läuft der Listener synchron im selben Thread, in dem das Event veröffentlicht wurde. Die Registrierung blockiert, bis die E-Mail versendet wurde (oder der Listener fertig ist).

// src/main/java/com/example/listeners/EmailNotificationListener.java

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class EmailNotificationListener {

    // Wird synchron im Thread der RegistrationService ausgefuehrt
    @EventListener
    public void handleRegistration(UserRegisteredEvent event) {
        System.out.println("-> [EMAIL] Sende Willkommens-E-Mail an: " + event.email());
        // ... (Simulierte E-Mail-Versand-Logik)
    }
}

b) Asynchroner Listener (Empfohlen für I/O)

Wenn der Listener I/O-intensive Aufgaben ausführt (wie E-Mail-Versand, API-Calls oder Datenbank-Updates), müssen wir ihn asynchron ausführen, um den Haupt-Thread der Anwendung nicht zu blockieren.

  1. Aktivieren wir Asynchronität in der Hauptklasse:
    @SpringBootApplication
    @EnableAsync // WICHTIG: Erlaubt asynchrone Ausfuehrung
    public class DemoApplication { /* ... */ }
    
  2. Dekorieren wir den Listener mit @Async:
    // src/main/java/com/example/listeners/StatsUpdateListener.java
    
    import org.springframework.context.event.EventListener;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
    
    @Component
    public class StatsUpdateListener {
    
        // Wird asynchron in einem separaten Thread ausgefuehrt
        @Async
        @EventListener
        public void updateStatistics(UserRegisteredEvent event) {
            System.out.println("-> [STATS] Aktualisiere Statistiken fuer User: " + event.userId());
            // ... (Kann lange dauern, blockiert aber die Registrierung nicht)
        }
    }
    

Zusätzliche Tricks für @EventListener

1. Bedingte Ausführung (Conditional Event Handling)

Wir können Spring Expression Language (SpEL) verwenden, um den Listener nur unter bestimmten Bedingungen auszuführen.

// Fuehrt Listener nur aus, wenn die E-Mail 'julian@example.com' enthaelt
@EventListener(condition = "#event.email.contains('julian@example.com')")
public void handleSpecialUser(UserRegisteredEvent event) {
    System.out.println("!!! Achtung: Special User registriert.");
}

2. Event-Hierarchie verarbeiten

Wenn unser Event von einer Superklasse erbt, können wir einen Listener definieren, der alle Unter-Events verarbeitet, oder einen spezifischeren Listener, der nur den genauen Event-Typ abfängt.


Fazit

Der Spring Event-Mechanismus mit ApplicationEvent und @EventListener ist eines der unterschätztesten Muster in Spring Boot. Er ist die interne, lose gekoppelte Alternative zur Nachrichtenvermittlung über externe Broker wie RabbitMQ oder Kafka.

Wir nutzen dieses Muster, wann immer wir Aufgaben haben, die nach einer Hauptaktion ausgeführt werden müssen, aber nicht Teil der Kernverantwortung der Hauptkomponente sind. Es ist der Schlüssel zu sauberem, wartbarem und flexiblen Code.