Frühlingsereignisse

1. Übersicht

In diesem Artikel wird die Verwendung von Ereignissen im Frühjahr erläutert .

Ereignisse sind eine der am meisten übersehenen Funktionen im Framework, aber auch eine der nützlicheren. Und - wie viele andere Dinge im Frühjahr - ist das Veröffentlichen von Ereignissen eine der Funktionen von ApplicationContext.

Es gibt einige einfache Richtlinien, die befolgt werden müssen:

  • Das Ereignis sollte ApplicationEvent erweitern
  • Der Herausgeber sollte ein ApplicationEventPublisher- Objekt einfügen
  • Der Listener sollte die ApplicationListener- Schnittstelle implementieren

2. Ein benutzerdefiniertes Ereignis

Mit Spring können wir benutzerdefinierte Ereignisse erstellen und veröffentlichen, die standardmäßig synchron sind . Dies hat einige Vorteile - wie zum Beispiel, dass der Listener am Transaktionskontext des Herausgebers teilnehmen kann.

2.1. Ein einfaches Anwendungsereignis

Erstellen wir eine einfache Ereignisklasse - nur einen Platzhalter zum Speichern der Ereignisdaten. In diesem Fall enthält die Ereignisklasse eine String-Nachricht:

public class CustomSpringEvent extends ApplicationEvent { private String message; public CustomSpringEvent(Object source, String message) { super(source); this.message = message; } public String getMessage() { return message; } }

2.2. Ein Verlag

Erstellen wir nun einen Herausgeber dieses Ereignisses . Der Herausgeber erstellt das Ereignisobjekt und veröffentlicht es für alle, die zuhören.

Um das Ereignis zu veröffentlichen, kann der Herausgeber einfach den ApplicationEventPublisher einfügen und die PublishEvent () - API verwenden:

@Component public class CustomSpringEventPublisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; public void publishCustomEvent(final String message) { System.out.println("Publishing custom event. "); CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message); applicationEventPublisher.publishEvent(customSpringEvent); } }

Alternativ kann die Herausgeberklasse die ApplicationEventPublisherAware- Schnittstelle implementieren. Dadurch wird auch der Ereignisverleger beim Start der Anwendung eingebunden. Normalerweise ist es einfacher, dem Publisher nur @Autowire zu injizieren .

2.3. Ein Zuhörer

Zum Schluss erstellen wir den Listener.

Die einzige Voraussetzung für den Listener ist, eine Bean zu sein und die ApplicationListener- Schnittstelle zu implementieren :

@Component public class CustomSpringEventListener implements ApplicationListener { @Override public void onApplicationEvent(CustomSpringEvent event) { System.out.println("Received spring custom event - " + event.getMessage()); } }

Beachten Sie, wie unser benutzerdefinierter Listener mit dem generischen Typ des benutzerdefinierten Ereignisses parametrisiert wird. Dadurch ist die Methode onApplicationEvent () typsicher. Dadurch muss auch nicht überprüft werden, ob das Objekt eine Instanz einer bestimmten Ereignisklasse ist, und es muss umgewandelt werden.

Wie bereits erläutert , blockiert die Methode doStuffAndPublishAnEvent () standardmäßig - Frühlingsereignisse sind synchron - , bis alle Listener die Verarbeitung des Ereignisses abgeschlossen haben.

3. Asynchrone Ereignisse erstellen

In einigen Fällen ist das synchrone Veröffentlichen von Ereignissen nicht das, wonach wir suchen. Möglicherweise müssen wir unsere Ereignisse asynchron behandeln .

Sie können dies in der Konfiguration aktivieren, indem Sie eine ApplicationEventMulticaster- Bean mit einem Executor erstellen . Für unsere Zwecke hier funktioniert SimpleAsyncTaskExecutor gut:

@Configuration public class AsynchronousSpringEventsConfig { @Bean(name = "applicationEventMulticaster") public ApplicationEventMulticaster simpleApplicationEventMulticaster() { SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); return eventMulticaster; } }

Das Ereignis, der Herausgeber und die Listener-Implementierungen bleiben unverändert. Jetzt behandelt der Listener das Ereignis asynchron in einem separaten Thread .

4. Bestehende Framework-Ereignisse

Spring selbst veröffentlicht eine Vielzahl von Veranstaltungen sofort. Beispielsweise löst der ApplicationContext verschiedene Framework-Ereignisse aus. ZB ContextRefreshedEvent, ContextStartedEvent, RequestHandledEvent usw.

Diese Ereignisse bieten Anwendungsentwicklern die Möglichkeit, sich in den Lebenszyklus der Anwendung und des Kontexts einzubinden und bei Bedarf ihre eigene benutzerdefinierte Logik hinzuzufügen.

Hier ist ein kurzes Beispiel für einen Listener, der auf Kontextaktualisierungen wartet:

public class ContextRefreshedListener implements ApplicationListener { @Override public void onApplicationEvent(ContextRefreshedEvent cse) { System.out.println("Handling context re-freshed event. "); } }

Weitere Informationen zu vorhandenen Framework-Ereignissen finden Sie in unserem nächsten Tutorial hier.

5. Annotation-Driven Event Listener

Ab Spring 4.2 muss ein Ereignis-Listener keine Bean sein, die die ApplicationListener- Schnittstelle implementiert. Er kann über die Annotation @EventListener für jede öffentliche Methode einer verwalteten Bean registriert werden :

@Component public class AnnotationDrivenEventListener { @EventListener public void handleContextStart(ContextStartedEvent cse) { System.out.println("Handling context started event."); } }

Nach wie vor deklariert die Methodensignatur den von ihr verwendeten Ereignistyp.

Standardmäßig wird der Listener synchron aufgerufen. Wir können es jedoch leicht asynchron machen, indem wir eine @ Async- Annotation hinzufügen . Wir müssen jedoch daran denken, die Async- Unterstützung in der Anwendung zu aktivieren .

6. Generika-Unterstützung

Es ist auch möglich, Ereignisse mit allgemeinen Informationen im Ereignistyp zu versenden.

6.1. Ein generisches Anwendungsereignis

Let's create a generic event type. In our example, the event class holds any content and a success status indicator:

public class GenericSpringEvent { private T what; protected boolean success; public GenericSpringEvent(T what, boolean success) { this.what = what; this.success = success; } // ... standard getters }

Notice the difference between GenericSpringEvent and CustomSpringEvent. We now have the flexibility to publish any arbitrary event and it's not required to extend from ApplicationEvent anymore.

6.2. A Listener

Now let’s create a listener of that event. We could define the listener by implementing the ApplicationListener interface like before:

@Component public class GenericSpringEventListener implements ApplicationListener
    
      { @Override public void onApplicationEvent(@NonNull GenericSpringEvent event) { System.out.println("Received spring generic event - " + event.getWhat()); } }
    

But unfortunately, this definition requires us to inherit GenericSpringEvent from the ApplicationEvent class. So for this tutorial, let's make use of an annotation-driven event listener discussed previously.

It is also possible to make the event listener conditional by defining a boolean SpEL expression on the @EventListener annotation. In this case, the event handler will only be invoked for a successful GenericSpringEvent of String:

@Component public class AnnotationDrivenEventListener { @EventListener(condition = "#event.success") public void handleSuccessful(GenericSpringEvent event) { System.out.println("Handling generic event (conditional)."); } }

The Spring Expression Language (SpEL) is a powerful expression language that's covered in detail in another tutorial.

6.3. A Publisher

The event publisher is similar to the one described above. But due to type erasure, we need to publish an event that resolves the generics parameter we would filter on. For example, class GenericStringSpringEvent extends GenericSpringEvent.

And there's an alternative way of publishing events. If we return a non-null value from a method annotated with @EventListener as the result, Spring Framework will send that result as a new event for us. Moreover, we can publish multiple new events by returning them in a collection as the result of event processing.

7. Transaction Bound Events

This paragraph is about using the @TransactionalEventListener annotation. To learn more about transaction management check out the Transactions with Spring and JPA tutorial.

Since Spring 4.2, the framework provides a new @TransactionalEventListener annotation, which is an extension of @EventListener, that allows binding the listener of an event to a phase of the transaction. Binding is possible to the following transaction phases:

  • AFTER_COMMIT (default) is used to fire the event if the transaction has completed successfully
  • AFTER_ROLLBACK – if the transaction has rolled back
  • AFTER_COMPLETION – if the transaction has completed (an alias for AFTER_COMMIT and AFTER_ROLLBACK)
  • BEFORE_COMMIT is used to fire the event right before transaction commit

Here's a quick example of transactional event listener:

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) public void handleCustom(CustomSpringEvent event) { System.out.println("Handling event inside a transaction BEFORE COMMIT."); }

This listener will be invoked only if there's a transaction in which the event producer is running and it's about to be committed.

And, if no transaction is running the event isn’t sent at all unless we override this by setting fallbackExecution attribute to true.

8. Conclusion

In diesem kurzen Tutorial haben wir die Grundlagen des Umgangs mit Ereignissen im Frühjahr erläutert - ein einfaches benutzerdefiniertes Ereignis erstellen, veröffentlichen und dann in einem Listener verarbeiten.

Wir haben uns auch kurz angesehen, wie die asynchrone Verarbeitung von Ereignissen in der Konfiguration aktiviert werden kann.

Anschließend erfuhren wir von Verbesserungen, die in Spring 4.2 eingeführt wurden, wie z. B. annotationsgesteuerte Listener, bessere Unterstützung für Generika und Ereignisse, die an Transaktionsphasen gebunden sind.

Wie immer ist der in diesem Artikel vorgestellte Code auf Github verfügbar. Dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein.