Eventos con Springboot

0
5558

Introducción

Los eventos nos permiten hacer que nuestra aplicación sea más flexible y desacoplada que si usaramos invocaciones a métodos directamente. Esto se hace registrando oyentes para los distintos eventos y, además, puede haber múltiples oyentes por evento.
Springboot ofrece una serie de herramientas para usar el patrón Observer sin tener que hacer ninguna configuración específica y a través de anotaciones.
Como ejemplo vamos a gestionar eventos con Springboot en la que se creen cursos. Cuando un curso sea creado se va a crear un evento y un consumidor de ese evento va a mostrar por consola el título del curso.

Indice

  1. Definiendo los eventos
  2. Publicando
  3. Escuchando
  4. Probando el ejemplo
  5. Soporte a transacciones
  6. Métodos asíncronos
  7. Referencias

Definiendo los eventos

Los eventos deben extender la clase ApplicationEvent.

public class Course extends ApplicationEvent {

}

Ahora bien, para hacernos las cosas más fáciles, si no extendemos de esta clase Spring de forma automática envolverá nuestro objeto en un PayloadApplicationEvent. Esta es la forma que se va a usar en este tutorial.

Publicando eventos

Para publicar eventos se emplea la interfaz ApplicationEventPublisher, que se introduce en un campo de la clase encargada de publicar los eventos. Spring se va a encargar de inyectar esta dependencia al instanciar CoursePublisher.

@Component
public class CoursePublisher {
    private final ApplicationEventPublisher publisher;

    public CoursePublisher(ApplicationEventPublisher publisher){
        this.publisher = publisher;
    }

    public void publish(){
      System.out.println("PUBLISHER: Producing course");
      publisher.publishEvent(new Course(generateRandomString()));
    }

    private String generateRandomString(){
        return RandomStringUtils.randomNumeric(5);
    }
}

El método publish es el encargado de publicar el evento del curso.

 Escuchando eventos

Un evento puede tener varios oyentes en distintos puntos de la aplicación, en función de la lógica de negocio. Es Spring el que se encarga de registrar los oyentes al iniciar la aplicación.
Hay dos formas de escuchar un evento: mediante anotaciones con @EventListener o implementando la clase ApplicationListener nosotros mismos. ApplicationListener define un único método, onApplicationEvent, que se llama cuando un evento es lanzado.

@Component
public class CourseConsumer {
    @EventListener
    public void createCourse(Course course) {
            System.out.println("CONSUMER: A course has been created with title: " + course.getTitle());
    }
}

Usando la anotación @EventListener hace que sea Spring el que registre un ApplicationListener para el evento concreto. La anotación de @Component está para que Spring lo detecte como Bean para luego inyectarlo donde sea necesario.

Probando el ejemplo

Para probar todo esto se puede crear un pequeño test que levante el contexto de Spring y ejecutarlo. Es necesario levantar el contexto para que se inyecten las dependencias de los @Autowired.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CourseConsumerTest {
    @Autowired
    private CourseConsumer courseConsumer;
    @Autowired
    private CoursePublisher coursePublisher;

    @Test
    public void testConsumerProducer(){
        courseProducer.publish();
        courseProducer.publish();
        courseProducer.publish();
    }
}

Las anotaciones usadas sobre la clase son: @RunWith que proporciona funcionalidad para los tests y @SpringBootTest que levanta el contexto de Spring. Al correr el test se observará en la consola el mensaje que CourseConsumer tiene definido.

PUBLISHER: Producing course
CONSUMER: A course has been created with title: 28435
PUBLISHER: Producing course
CONSUMER: A course has been created with title: 08887
PUBLISHER: Producing course
CONSUMER: A course has been created with title: 80268

Soporte a transacciones

Mediante el uso de la anotación @TransactionalEventListener se consigue un oyente que va a tener en cuenta si el evento se encuentra en una transacción. ¿Cuándo se va a ejecutar el método del oyente? Se puede especificar la fase en la que se quiere invocar utilizando el argumento phase. Existen cuatro opciones:

  • AFTER_COMMIT es la opción por defecto. Se invoca una vez el commit ha finalizado de forma exitosa.
  • AFTER_COMPLETION se invoca cuando la transacción se ha completado.
  • AFTER_ROLLBACK se invoca solo si la transacción ha terminado en un roll back.
  • BEFORE_COMMIT se invoca antes de que la transacción haga el commit.

Métodos asíncronos

Spring permite crear y publicar eventos de forma síncrona por defecto. Esto tiene la ventaja de permitir al oyente participar en el contexto de la transacción del evento.

Si queremos un comportamiento asíncrono lo que hay que hacer es anotar el método del Bean que queremos que sea asíncrono con @Async. De este modo ese método se ejecutará en otro hilo y el que llama al método no esperará a recibir una respuesta para seguir con la ejecución. También hay que añadir en la configuración la anotación @EnableAsync.

@SpringBootApplication
@EnableAsync
public class App {
    private static ApplicationContext applicationContext;

    public static void main(String[] args) {
        applicationContext = SpringApplication.run(App.class, args);
    }
}

Vamos a anotar el método de publish como asíncrono. Además se añade una espera de 10 segundos antes de publicar el evento.

@Component
public class CoursePublisher {
    private final ApplicationEventPublisher publisher;

    public CoursePublisher(ApplicationEventPublisher publisher){
        this.publisher = publisher;
    }

    @Async
    public void publish() throws InterruptedException {
        System.out.println("PUBLISHER: Producing course");
        Thread.sleep(10000);
        publisher.publishEvent(new Course(generateRandomString()));
    }

    private String generateRandomString(){
        return RandomStringUtils.randomNumeric(5);
    }
}

En el test hay que incluir otro tiempo (de 11 segundos) de espera para que no se cierre la terminal.

    @Test
    public void testConsumerProducer() throws InterruptedException {
        courseProducer.publish();
        courseProducer.publish();
        courseProducer.publish();
        Thread.sleep(11000);
    }

El resultado en la consola es:

PUBLISHER: Producing course
PUBLISHER: Producing course
PUBLISHER: Producing course
CONSUMER: A course has been created with title: 71685
CONSUMER: A course has been created with title: 81937
CONSUMER: A course has been created with title: 44439

No se ha esperado a que el primer publisher termine para llamar al siguiente, si no que ha tenido un comportamiento asíncrono. Como se ha visto, configurar los eventos con Springboot para que sean asíncronos es sencillo.

Referencias

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad