Spring events con anotaciones
En Paso de eventos entre beans con SpringFramework Core vimos un ejemplo sencillo de cómo pasar eventos en spring framework. Se hizo sin usar anotaciones y usando clases de Spring. Vamos a verlo ahora de forma parecida, pero usando anotaciones. Tiene el ejemplo completo en spring-event
El evento[editar]
El evento que queramos pasar de un sitio a otro no es más que una clase java cualquiera que tenga la información que necesitemos pasar. En nuestro ejemplo, es bien sencilla
package com.chuidiang.examples.spring_events;
/**
* @author fjabellan
* @date 20/12/2021
*/
public class MyEvent {
public String text;
public MyEvent(String text){
this.text = text;
}
}
Publicar el evento[editar]
La clase que publica el evento debe recibir un ApplicationEventPublisher
, habitualmente por inyección de dependencias. La siguiente clase puede ser un ejemplo
package com.chuidiang.examples.spring_events;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* @author fjabellan
* @date 20/12/2021
*/
@Component
@EnableScheduling
public class Publisher {
@Autowired
ApplicationEventPublisher publisher;
@Scheduled(fixedRate = 1000)
public void init() {
publisher.publishEvent(new MyEvent("texto del evento"));
System.out.println("ya");
}
}
Veamos algunos detalles.
- La anotación @Component sirve para que spring sepa que tiene que instanciar esta clase e inyectarle las dependencias que necesite.
- La anotación @EnableScheduling no es en absoluto necesaria para publicar eventos. La usamos en nuestro ejemplo para poder lanzar el método de publicación periódicamente y ver el ejemplo funcionando.
- Se pone @Autowired de ApplicationEventPublisher, de forma que spring inyecte esta dependencia tras instanciar esta clase.
- El método init() se anota con @Scheduled(fixedRate = 1000). De esta forma, se ejecutará automáticamente cada sengudo (1000 mili segundos).
- publisher.publishEvent(...) publica nuestro evento.
Suscribirse al evento[editar]
Para suscribirse al evento, nada tan sencillo como crear una clase que instancie spring, ponerle un método que tenga como parámetro la clase del evento que queremos recibir y anotarla con @EventListener, como en el siguiente ejemplo
package com.chuidiang.examples.spring_events;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* @author fjabellan
* @date 20/12/2021
*/
@Component
public class Listener1 {
@EventListener
public void onEvent(MyEvent event){
System.out.println("Listener 1 " + event.text);
}
}
Detalles:
- @Component para que spring sepa que tiene que instanciarla
- Método que recibe MyEvent y anotado con @EventListener. Cuando alguien publique un evento de este tipo, nos llamará a este método.
Y no hay más truco
Juntando todo[editar]
Para meter y arrancar todo esto en un main, vamos con un spring framework tradicional (no SpringBoot). Necesitamos un fichero xml de Spring que simplemente diga que haga caso a las anotaciones del código
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.chuidiang.examples.spring_events"/>
</beans>
En nuestro ejemplo, lo hemos metido en el directorio src/main/resources del proyecto, por lo que estará accesible desde el classpath de java. El main puede ser así de sencillo
package com.chuidiang.examples.spring_events;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author fjabellan
* @date 20/12/2021
*/
public class SpringEventsExample {
public static void main(String[] args) throws InterruptedException {
new ClassPathXmlApplicationContext("spring-context.xml").start();
}
}
Solo un main que carga el fichero spring xml del classpath. Arrancandolo, tenemos todo funcionando, veremos que Publisher emite el evento cada segundo y Listener1 lo recibe.
Mecanismo de notificaciones[editar]
Tal cual está aquí, spring avisa a todos los listener uno por uno y no avisa al siguiente hasta que el anterior ha acabado. Es por ello importante que:
- Un listener no tarde mucho o se quede bloqueado. No se avisará al resto de listeners y se queda parado el mecanismo de notificaciones.
- Un listener no lance una excepción no controlada. Esto hará que se rompa el bucle de aviso y no se avise al resto de listeners.
Existe un mecanismo de notificaciones asíncrono, de forma que a cada listener se le avisa en su propio hilo, pudiendo de esta manera tardar mucho o incluso dar excepciones. Pero esto es tema para otro artículo.