Spring events con anotaciones

De ChuWiki

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.