Llamadas asíncronas con @Async en Spring

@async con spring boot

@async con spring boot


En esta nueva entrada de refactorizando vamos a ver como podemos realizar llamadas asíncronas con @Async en Spring, la cual nos permitirá procesar y ejecutar otros métodos en un nuevo thread. Es decir, no vamos a tener que esperar por la ejecución de la otra parte de nuestro código.

¿Qué es @Async?

El uso de @Async nos va a proporcionar y permitir el uso de programación asíncrona con Java, la cual es una técnica de programación que permite el procesamiento paralelo y separar la carga de trabajo del thread principal creando nuevos threads worker.

Configuración @Async en Spring

Lo primero que debemos de realizar en nuestra aplicación Spring es activar el uso de @Async para ello vamos a hacer uso de la anotación @EnableAsync la cual dotará a nuestra aplicación de ejecutar métodos en background de manera asíncrona.

@EnableAsync
@SpringBootApplication
public class Application extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(Application.class);
	}

Con la anterior anotacion @EnableAsync ya tendríamos nuestra aplicación preparada para procesos asíncronos. Aunque si queremos configurar un poco la aplicación lo podemos hacer con los siguientes parámetros:

  • mode indica el tipo de advice que se usará (proxy o AspectJ).
  • proxyTargetClass indica el proxy que será usado.
  • order indica el orden en el que el  AsyncAnnotationBeanPostProcessor se aplicará, por defecto es el último.

El método sobre el que se aplique la anotación deberá ser siempre PUBLIC ya que de esta manera se puede aplicar el proxy.

Uso de Async con void

Una de las formas de hacer uso de @Async es invocando a un método que devolverá void, de esta manera no esperamos ningún resultado y el hilo principal no esperará resultado del worker thread.

Un ejemplo de uso de @Async podría ser el siguiente:

@Async
public void useofAsynWithVoid(user user) {
    User userSaved = userRepository.save(user);
}

El ejemplo anterior creará un nuevo Thread cada vez que ese método sea invocado.

Uso de Async con Future y CompletableFuture

Vamos a ver como podemos utilizar @Async con Future y CompletableFuture.

Uso de Async con Future

La interfaz Future fue añadida hace ya algún tiempo en la versión 5 de Java para poder otorgar a Java de procesos asíncronos, pero quizás se quedo algo corto al no poder proporcionar posibilidades de combinar esos resultados o tener tratamiento de errores.

Para poder encapsular y devolver un Future en nuestras peticiones haciendo uso de @Async lo podemos hacer de la siguiente forma:

@Async
public Future<String> useofAsynWithFuture() {

    User userSaved = userRepository.save(user);

   return new AsyncResult<User>(userSaved);

}

Uso de Async con CompletableFuture

Aunque el uso de Future nos va a ayudar a devolver resultados asíncronos, que sucede si queremos hacer tratamiento de errores o si queremos combinar llamadas?, que no vamos a poder. Por ello apareció en Java 8 CompletableFuture.

CompletableFuture que además implementa la interfaz Future, nos va a permitir combinar, ejecutar y tratar errores a través de sus múltiples métodos los procesos asíncronos.

Vamos a ver un uso de CompletableFuture pero hay que tener en cuenta que esta funcionalidad tiene un gran número de métodos y posibilidades para realizar tratamiento de futuros. Además el uso de CompletableFuture nos permite realizar peticiones asíncronas sin necesidad de hacer uso de @Async:

@Async("myExecutor")
public CompletableFuture<User> findUser(String usernameString) throws InterruptedException {
        User myResult = doSomething(usernameString);
        return CompletableFuture.completedFuture(myResult);
}

Por ejemplo el código anterior sin @Async sería:

CompletableFuture<User> future = CompletableFuture.runAsync(() -> doSomething(userNameString));

Customización de Executor de Spring

Una vez activado el uso de procesamientos asíncronos en nuestra aplicación Spring, se hará uso de SimpleAsyncTaskExecutor el cual nos añade una configuración por defecto en nuestros procesos asíncronos. Pero a veces vamos a necesitar realizar una configuración diferente por lo que podemos hacerlo o bien de manera global a toda nuestra aplicación o personalizarlo para una petición asíncrona en concreto.

Por ejemplo si queremos crear un Executor para un @Async en croncreto, crearíamos un bean de la siguiente manera:

@Bean(name = "userPoolTaskExecutor")
public Executor userPoolTaskExecutor() {
    final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(4);
    executor.setMaxPoolSize(4);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("user-");
    executor.initialize();
    return executor;
}

Y ahora nuestro @Async lo utilizaría de la siguiente manera:

@Async("userPoolTaskExecutor")
public void transcodeVideo(User user) {
    userRepository.save(user);
}

En cambio si queremos modificar el Executor y usarlo de manera global tendríamos que hacer la implementación de la clase AsyncConfigurer de la siguiente manera:

@Configuration
@EnableAsync
public class CustomExecutor implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
       final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       executor.setCorePoolSize(4);
       executor.setMaxPoolSize(4);
       executor.setQueueCapacity(500);
       executor.setThreadNamePrefix("user-");
       executor.initialize();
       return executor;    
   }
    
}

Gestión de Errores en Async

Uno de los aspectos a tener en cuenta cuando hacemos uso de @Async es como vamos a realizar la gestión de los errores. Cuando hacemos uso de @Async con Future o CompletableFuture no vamos a tener problema en realizar una gestión de errores ya que al método al que llamamos nos devuelve un valor; en cambio cuando hacemos uso void en un método con @Async se complica un poco el poder realizar esa gestión ya que el error no se propaga al thread principal.

Para poder realizar la gestión de excepciones cuando trabajamos con @Async vamos a realizar dos pasos:

  • Implementar AsyncUncaughtExceptionHandler sobre escribiendo el método handleUncaughtException
  • Implementar la clase AsyncConfigurer e instanciar la clase que hemos creado.

Vamos a verlo en código:

public class CustomAsyncErrorHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
 
        log.error("Error in the asynchronous call " + throwable.getMessage());

        log.error("Error in the method " + method.getName());

    }
    
}
@Configuration
@EnableAsync
public class CustomAsync implements AsyncConfigurer {

  @Override
  public CustomAsyncErrorHandler getAsyncUncaughtExceptionHandler() {
      return new CustomAsyncExceptionHandler();
  }

}

Conclusión

En esta entrada sobre llamadas asíncronas con @Async en Spring hemos visto como podemos crear una aplicación asíncrona en Spring lo cual será de gran utilidad para realizar procesamiento paralelo. Un aspecto a tener en cuenta es que debemos evitar confundir una aplicación asíncrona con una aplicación reactiva (la cual delega el procesamiento a un thread pool).

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!


1 pensamiento sobre “Llamadas asíncronas con @Async en Spring

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *