Tips Spring : [CORE, BOOT] Ejecución asíncrona de métodos con @Async

Última actualización: 01/10/2022
logo spring

Spring permite la ejecución asíncrona de métodos de los beans. Se ejecutarán en su propio hilo y, por tanto, sin bloquear al código que realiza la llamada. Y lo mejor de todo es que esta funcionalidad es muy fácil de utilizar: basta con aplicar la anotación @Async, disponible desde Spring 3.0.

@Service
public class AsyncService {
	
	@Async
	public void executeAsync() {
		System.out.println("Hello, I'm async!!!");
	}

Tanto si usamos Spring Boot como si no, es imprescindible activar la aplicación de @Async con la anotación @EnableAsync en una clase de configuración (esto es, una marcada con @Configuration o @SpringBootApplication). De lo contrario, @Async no tiene el más mínimo efecto.

@Configuration
@EnableAsync
public class AsyncApplication {

La equivalencia en XML.

<task:annotation-driven />

No es necesario hacer nada más. Ahora, los métodos @Async serán asíncronos. Eso sí, solo es posible si se llaman desde otra clase. Si invocamos un método @Async desde otro situado en su misma clase, no obtendremos la deseada asincronía. En estos casos, la solución más sencilla consiste en refactorizar y ubicar los métodos en clases distintas.

Otro punto de interés es el retorno de los métodos asíncronos. Pueden devolver cualquier cosa o void, pero si queremos recibir el resultado o una posible excepción, hay que envolverlo en un objeto de tipo Future. Para ayudarnos, Spring proporciona una implementación propia llamada AsyncResult.

Internamente, la ejecución asíncrona es realizada dentro de un Runnable por una implementación de TaskExecutor. Spring crea una forma automática, pero por lo común vamos a querer configurarla según las necesidades de nuestro proyecto. Lo que haremos es crear un TaskExecutor como un bean de Spring. Esto lo hacemos en un método «factoría»: método ni privado ni final, anotado con @Bean y situado en una clase de configuración. El nombre del bean coincide con el del método; recomiendo usar taskExecutor para asegurar que será la implementación predeterminada. Como alternativa, podemos elegir cualquier otro nombre y marcar el método factoría con @Primary.

En el siguiente ejemplo he usado ThreadPoolTaskExecutor, la implementación de TaskExecutor más potente.

@Bean
TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2); //default: 1
    executor.setMaxPoolSize(10); //default: Integer.MAX_VALUE
    executor.setQueueCapacity(20); // default: Integer.MAX_VALUE
    executor.setKeepAliveSeconds(120); // default: 60 seconds
    executor.initialize();
   return executor;
}

La configuración equivalente en XML es la siguiente.

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="2"/>
    <property name="maxPoolSize" value="10"/>
    <property name="queueCapacity" value="20"/>
    <property name="keepAliveSeconds" value="120"/>
</bean>

Nota. Es recomendable definir los parámetros de configuración fuera del código siguiendo el tutorial Ficheros de propiedades en Spring.

El ejemplo configura un pool de hilos dinámico con un tamaño comprendido entre 2 y 10, y una cola de peticiones de tamaño 20. Un hilo desocupado permanecerá en el pool un máximo de 120 segundos.

La ejecución de un método asíncrono requiere el uso de un hilo del pool. Esto significa que no pueden coincidir al mismo tiempo más de 10 métodos en ejecución. En ese caso, las nuevas peticiones que vayan llegando quedan en una cola a la espera de que se libere algún hilo. Cuando la cola contenga 20 peticiones, las siguientes solicitudes se descartarán.

Spring Boot aporta dos alternativas para configurar el TaskExecutor predeterminado.

1-. Crearlo de forma programática tal y como ya hicimos, pero con la clase TaskExecutorBuilder

@Bean
public TaskExecutor taskExecutor(TaskExecutorBuilder builder) {
  return builder.corePoolSize(2)
			.maxPoolSize(10)
			.queueCapacity(20)
            .keepAlive(Duration.ofSeconds(120))
			.build();
}

2-. Usar las propiedades de tipo spring.task.execution.pool en el fichero application.properties (o application.yml si prefieres este formato).

spring.task.execution.pool.core-size=2
spring.task.execution.pool.max-size=10
spring.task.execution.pool.queue-capacity=20
spring.task.execution.pool.keep-alive=120

¿Necesitas más de un pool con configuraciones distintas? No es problema. Puedes crear los TaskExecutor que quieras e indicar en @Async el que debe usarse. Si no, se tomará el predeterminado.

@Bean
TaskExecutor taskExecutor2() {
       ....
 }
@Async("taskExecutor2")
public void doSomething() {
    ...
}

Otros tutoriales relacionados con Spring

Introducción a Spring Boot: Aplicación Web con servicios REST y Spring Data JPA

Spring Boot: Gestión de errores en aplicaciones web y REST

Testing en Spring Boot con JUnit 45. Mockito, MockMvc, REST Assured, bases de datos embebidas

Spring JDBC Template: simplificando el uso de SQL

Persistencia en BD con Spring Data JPA

Ficheros .properties en Spring IoC

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.