Eventi di primavera

1. Panoramica

In questo articolo, discuteremo come utilizzare gli eventi in primavera .

Gli eventi sono una delle funzionalità più trascurate nel framework ma anche una delle più utili. E, come molte altre cose in primavera, la pubblicazione di eventi è una delle funzionalità fornite da ApplicationContext.

Ci sono alcune semplici linee guida da seguire:

  • l'evento dovrebbe estendere ApplicationEvent
  • l'editore dovrebbe inserire un oggetto ApplicationEventPublisher
  • l'ascoltatore dovrebbe implementare l' interfaccia ApplicationListener

2. Un evento personalizzato

La primavera ci consente di creare e pubblicare eventi personalizzati che, per impostazione predefinita, sono sincroni . Ciò presenta alcuni vantaggi, come, ad esempio, la possibilità per l'ascoltatore di partecipare al contesto della transazione dell'editore.

2.1. Un semplice evento applicativo

Creiamo una semplice classe di eventi , solo un segnaposto per memorizzare i dati dell'evento. In questo caso, la classe dell'evento contiene un messaggio 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 editore

Ora creiamo un editore di quell'evento . L'editore costruisce l'oggetto evento e lo pubblica a chiunque sia in ascolto.

Per pubblicare l'evento, l'editore può semplicemente inserire ApplicationEventPublisher e utilizzare 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); } }

In alternativa, la classe publisher può implementare l' interfaccia ApplicationEventPublisherAware , che inserirà anche l'editore dell'evento all'avvio dell'applicazione. Di solito, è più semplice iniettare al publisher @Autowire.

2.3. Un ascoltatore

Infine, creiamo l'ascoltatore.

L'unico requisito per il listener è essere un bean e implementare l' interfaccia ApplicationListener :

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

Si noti come il nostro listener personalizzato sia parametrizzato con il tipo generico di evento personalizzato, il che rende il metodo onApplicationEvent () indipendente dai tipi. Ciò evita anche di dover controllare se l'oggetto è un'istanza di una specifica classe di eventi e di eseguirne il cast.

E, come già discusso, per impostazione predefinita gli eventi primaverili sono sincroni , il metodo doStuffAndPublishAnEvent () si blocca finché tutti i listener non terminano l'elaborazione dell'evento.

3. Creazione di eventi asincroni

In alcuni casi, la pubblicazione di eventi in modo sincrono non è proprio quello che stiamo cercando: potremmo aver bisogno di una gestione asincrona dei nostri eventi .

È possibile attivarlo nella configurazione creando un bean ApplicationEventMulticaster con un esecutore; per i nostri scopi qui SimpleAsyncTaskExecutor funziona bene:

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

L'evento, l'editore e le implementazioni del listener rimangono gli stessi di prima, ma ora il listener gestirà l'evento in modo asincrono in un thread separato .

4. Eventi Framework esistenti

La stessa primavera pubblica una varietà di eventi fuori dagli schemi. Ad esempio, ApplicationContext attiverà vari eventi del framework. Ad esempio ContextRefreshedEvent, ContextStartedEvent, RequestHandledEvent ecc.

Questi eventi forniscono agli sviluppatori di applicazioni un'opzione per agganciarsi al ciclo di vita dell'applicazione e al contesto e aggiungere la propria logica personalizzata dove necessario.

Ecco un rapido esempio di un ascoltatore che ascolta gli aggiornamenti del contesto:

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

Per saperne di più sugli eventi del framework esistenti, dai un'occhiata al nostro prossimo tutorial qui.

5. Listener di eventi guidato dall'annotazione

Starting with Spring 4.2, an event listener is not required to be a bean implementing the ApplicationListener interface – it can be registered on any public method of a managed bean via the @EventListener annotation:

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

As before, the method signature declares the event type it consumes.

By default, the listener is invoked synchronously. However, we can easily make it asynchronous by adding an @Async annotation. We must remember to enable Async support in the application, though.

6. Generics Support

It is also possible to dispatch events with generics information in the event type.

6.1. A Generic Application Event

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 questo breve tutorial, abbiamo esaminato le basi per gestire gli eventi in primavera : creare un semplice evento personalizzato, pubblicarlo e quindi gestirlo in un listener.

Abbiamo anche dato una breve occhiata a come abilitare l'elaborazione asincrona degli eventi nella configurazione.

Quindi abbiamo appreso i miglioramenti introdotti nella primavera 4.2, come i listener guidati dalle annotazioni, un migliore supporto per i generici e gli eventi che si legano alle fasi delle transazioni.

Come sempre, il codice presentato in questo articolo è disponibile su Github. Questo è un progetto basato su Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.