Événements de printemps

1. Vue d'ensemble

Dans cet article, nous allons discuter de l'utilisation des événements au printemps .

Les événements sont l'une des fonctionnalités les plus négligées du framework mais aussi l'une des plus utiles. Et - comme beaucoup d'autres choses dans Spring - la publication d'événements est l'une des fonctionnalités fournies par ApplicationContext.

Il y a quelques directives simples à suivre:

  • l'événement doit étendre ApplicationEvent
  • l'éditeur doit injecter un objet ApplicationEventPublisher
  • l'écouteur doit implémenter l' interface ApplicationListener

2. Un événement personnalisé

Spring nous permet de créer et de publier des événements personnalisés qui - par défaut - sont synchrones . Cela présente quelques avantages, comme par exemple la possibilité pour l'auditeur de participer au contexte de transaction de l'éditeur.

2.1. Un événement d'application simple

Créons une classe d'événement simple - juste un espace réservé pour stocker les données d'événement. Dans ce cas, la classe d'événements contient un message String:

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. Un éditeur

Créons maintenant un éditeur de cet événement . L'éditeur construit l'objet événement et le publie à tous ceux qui l'écoutent.

Pour publier l'événement, l'éditeur peut simplement injecter l' ApplicationEventPublisher et utiliser l' API publishEvent () :

@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); } }

Sinon, la classe éditeur peut implémenter l' interface ApplicationEventPublisherAware - cela injectera également l'éditeur d'événements au démarrage de l'application. Habituellement, il est plus simple d'injecter simplement à l'éditeur @Autowire.

2.3. Un auditeur

Enfin, créons l'auditeur.

La seule exigence pour l'écouteur est d'être un bean et d'implémenter l' interface ApplicationListener :

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

Remarquez comment notre écouteur personnalisé est paramétré avec le type générique d'événement personnalisé - ce qui rend la méthode onApplicationEvent () sûre de type. Cela évite également d'avoir à vérifier si l'objet est une instance d'une classe d'événement spécifique et à la lancer.

Et, comme déjà discuté - par défaut les événements de printemps sont synchrones - la méthode doStuffAndPublishAnEvent () se bloque jusqu'à ce que tous les écouteurs aient fini de traiter l'événement.

3. Création d'événements asynchrones

Dans certains cas, publier des événements de manière synchrone n'est pas vraiment ce que nous recherchons - nous pouvons avoir besoin d'une gestion asynchrone de nos événements .

Vous pouvez l'activer dans la configuration en créant un bean ApplicationEventMulticaster avec un exécuteur; pour nos besoins ici SimpleAsyncTaskExecutor fonctionne bien:

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

L'événement, l'éditeur et les implémentations de l'écouteur restent les mêmes qu'auparavant - mais maintenant, l'écouteur traitera l'événement de manière asynchrone dans un thread séparé .

4. Événements du cadre existant

Spring lui-même publie une variété d'événements prêts à l'emploi. Par exemple, ApplicationContext déclenchera divers événements de structure. Par exemple, ContextRefreshedEvent, ContextStartedEvent, RequestHandledEvent, etc.

Ces événements offrent aux développeurs d'applications la possibilité de se connecter au cycle de vie de l'application et du contexte et d'ajouter leur propre logique personnalisée si nécessaire.

Voici un exemple rapide d'un auditeur à l'écoute des actualisations de contexte:

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

Pour en savoir plus sur les événements de framework existants, jetez un œil à notre prochain tutoriel ici.

5. Écouteur d'événement basé sur les annotations

À partir de Spring 4.2, un écouteur d'événements n'est pas obligé d'être un bean implémentant l' interface ApplicationListener - il peut être enregistré sur n'importe quelle méthode publique d'un bean géré via l' annotation @EventListener :

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

Comme précédemment, la signature de méthode déclare le type d'événement qu'elle consomme.

Par défaut, l'écouteur est appelé de manière synchrone. Cependant, nous pouvons facilement le rendre asynchrone en ajoutant une annotation @Async . Nous devons cependant nous rappeler d'activer le support Async dans l'application.

6. Prise en charge des génériques

Il est également possible d'envoyer des événements avec des informations génériques dans le type d'événement.

6.1. Un événement d'application générique

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

Dans ce rapide didacticiel, nous avons passé en revue les bases de la gestion des événements dans Spring : créer un événement personnalisé simple, le publier, puis le gérer dans un écouteur.

Nous avons également examiné brièvement comment activer le traitement asynchrone des événements dans la configuration.

Ensuite, nous avons découvert les améliorations apportées à Spring 4.2, telles que les écouteurs basés sur les annotations, une meilleure prise en charge des génériques et des événements liés aux phases de transaction.

Comme toujours, le code présenté dans cet article est disponible à l'adresse over sur Github. Il s'agit d'un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.