1. Introducción

En este post veremos cómo configurar SpringBoot para crear hilos asíncronos en los métodos de nuestros beans de Spring. Esto permitirá mejorar el rendimiento de nuestras aplicaciones ya que toda la operativa que no sea indispensable para poder terminar nuestro hilo principal, no impactará en el rendimiento del mismo. Os podéis descargar el código de ejemplo de mi GitHub aquí.

Tecnologías empleadas:

  • Java 8
  • Gradle 3.1
  • SpringBoot 1.5.2.RELEASE
  • Spring 4.3.7.RELEASE

2. Configuración

Se muestra el build.gradle

group 'com.jorgehernandezramirez.spring.springboot'
version '1.0-SNAPSHOT'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'

sourceCompatibility = 1.8

springBoot {
    mainClass = "com.jorgehernandezramirez.spring.springboot.async"
}

repositories {
    mavenCentral()
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
}

Clase principal de configuración. Tendremos que añadir las anotaciones @EnableAsync y @EnableScheduling. Además deberemos crear un bean que implemente la interfaz Executor.

package com.jorgehernandezramirez.spring.springboot.async.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@EnableAsync
@EnableScheduling
@Configuration
public class AsyncConfiguration extends AsyncConfigurerSupport {

    public AsyncConfiguration(){
        //For Spring
    }

    @Override
    public Executor getAsyncExecutor() {
        final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("JorgeHernandezRamirez-");
        executor.initialize();
        return executor;
    }
}

Main que permite arrancar SpringBoot.

package com.jorgehernandezramirez.spring.springboot.async;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public Application(){
        //For Spring
    }

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

3. @Async

La anotación @Async es la que nos permitirá crear los métodos asíncronos de nuestros servicios .

A continuación se muestra un servicio de login que nos permitirá testear el comportamiento de dicha anotación. La api define dos métodos:

  • doLogin -> Queremos que sea un método síncrono
  • refreshUserData -> Queremos que sea un método asíncrono
package com.jorgehernandezramirez.spring.springboot.async.service.api;


/**
 * Api de login del usuario
 */
public interface ILoginService {

    /**
     * Método que realiza el login del usuario
     * @param principal
     * @param credenctial
     * @return
     */
    String doLogin(String principal, String credenctial);

    /**
     * Método que refresca los datos del usuario
     * @param principal
     */
    void refreshUserData(String principal);
}

Implementación dummy del servicio de login. Insistir en el uso de @Async en el método refreshUserData para que sea ejecutado de forma asíncrona.

package com.jorgehernandezramirez.spring.springboot.async.service.impl;

import com.jorgehernandezramirez.spring.springboot.async.service.api.ILoginService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class LoginDummyService implements ILoginService {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoginDummyService.class);

    private static final String DUMMYTOKEN = "DUMMYTOKEN";

    public LoginDummyService(){
        //Para Spring
    }

    @Override
    public String doLogin(String principal, String credenctial) {
        LOGGER.info("Realizando login del usuario {}", principal);
        return DUMMYTOKEN;
    }

    @Async
    @Override
    public void refreshUserData(String principal) {
        try {
            Thread.sleep(10000);
            LOGGER.info("Se ha refrescado los datos del usuario {}", principal);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

Controlador login para testear nuestro servicio.

package com.jorgehernandezramirez.spring.springboot.async.controller;

import com.jorgehernandezramirez.spring.springboot.async.service.api.ILoginService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/login")
public class LoginController {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);

    @Autowired
    private ILoginService loginService;

    public LoginController(){
        //Para Spring
    }

    @RequestMapping(method = RequestMethod.POST)
    public String getUsers(@RequestParam("username") String username,
                @RequestParam("password") final String password){
        final String tokenToReturn = loginService.doLogin(username, password);
        loginService.refreshUserData(username);
        LOGGER.info("Procediendo a devolver los usuarios del sistema...");
        return tokenToReturn;
    }
}

4. Probando la aplicación

curl -X POST -d "username=jorge&password=jorge" http://localhost:8080/login

La respuesta obtenida es

DUMMYTOKEN

En consola se deberá escribir 10 segundos después

Se ha refrescado los datos del usuario jorge