web-dev-qa-db-fra.com

La méthode Spring Boot @Async du contrôleur s'exécute de manière synchrone

Mon application [de base] Spring Boot accepte une demande du navigateur, envoyée via jQuery.get() et est supposée recevoir immédiatement une réponse, telle que "votre demande a été mise en attente". Pour ce faire, j'ai écrit un contrôleur:

@Controller
public class DoSomeWorkController {

  @Autowired
  private final DoWorkService workService;

  @RequestMapping("/doSomeWork")
  @ResponseBody
  public String doSomeWork() {

    workService.doWork(); // time consuming operation
    return "Your request has been queued.";
  }
}

La classe DoWorkServiceImpl implémente une interface DoWorkService et est très simple. Il a une seule méthode pour effectuer une tâche fastidieuse. Je n'ai besoin de rien en retour de cet appel de service, car un courrier électronique sera envoyé à la fin des travaux, que ce soit en cas d'échec ou de réussite. Donc, cela ressemblerait effectivement à:

@Service
public class DoWorkServiceImpl implements DoWorkService {

  @Async("workExecutor")
  @Override
  public void doWork() {

    try {
        Thread.sleep(10 * 1000);
        System.out.println("completed work, sent email");
    }
    catch (InterruptedException ie) {
        System.err.println(ie.getMessage());
    }
  }
}

Je pensais que cela fonctionnerait, mais la requête Ajax du navigateur a attendu 10 secondes avant de renvoyer la réponse. Il semble donc que la méthode mappée par le contrôleur appelle la méthode interne annotée avec @Async de manière synchrone. Dans une application Spring traditionnelle, j'ajoute généralement ceci à la configuration XML:

<task:annotation-driven />
<task:executor id="workExecutor" pool-size="1" queue-capacity="0" rejection-policy="DISCARD" />

J'ai donc pensé qu'écrire l'équivalent de ceci dans la classe d'application principale aiderait:

@SpringBootApplication
@EnableAsync
public class Application {

  @Value("${pool.size:1}")
  private int poolSize;;

  @Value("${queue.capacity:0}")
  private int queueCapacity;

  @Bean(name="workExecutor")
  public TaskExecutor taskExecutor() {
      ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
      taskExecutor.setMaxPoolSize(poolSize);
      taskExecutor.setQueueCapacity(queueCapacity);
      taskExecutor.afterPropertiesSet();
      return taskExecutor;
  }

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

Cela n'a pas changé le comportement. La réponse Ajax arrive toujours après 10 secondes d’envoi de la demande. Qu'est-ce que je rate?

L’application Spring Boot peut être téléchargée ici . Avec Maven installé, le projet peut être exécuté avec la commande simple:

mvn clean spring-boot:run

Note Le problème a été résolu grâce à la réponse fournie par @Dave Syer ci-dessous, qui a indiqué que je manquais de @EnableAsync dans ma candidature, même si j'avais la ligne dans l'extrait de code ci-dessus.

20
Web User

Vous appelez la méthode @Async à partir d'une autre méthode de la même classe. Sauf si vous activez le mode proxy AspectJ pour le @EnableAsync (et fournissez bien sûr un tisserand) qui ne fonctionnera pas (google "proxy self-invocation"). La solution la plus simple consiste à placer la méthode @Async dans un autre @Bean.

37
Dave Syer

Pour tous ceux qui recherchent encore toutes les étapes de @Asnyc expliquées de manière simple, voici la réponse:

Voici un exemple simple avec @Async. Suivez ces étapes pour que @Async fonctionne dans votre application Spring Boot:

Étape 1: Ajoutez une annotation @EnableAsync et ajoutez un bean TaskExecutor à la classe d'application.

Exemple:

@SpringBootApplication
@EnableAsync
public class AsynchronousSpringBootApplication {

    private static final Logger logger = LoggerFactory.getLogger(AsynchronousSpringBootApplication.class);

    @Bean(name="processExecutor")
    public TaskExecutor workExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("Async-");
        threadPoolTaskExecutor.setCorePoolSize(3);
        threadPoolTaskExecutor.setMaxPoolSize(3);
        threadPoolTaskExecutor.setQueueCapacity(600);
        threadPoolTaskExecutor.afterPropertiesSet();
        logger.info("ThreadPoolTaskExecutor set");
        return threadPoolTaskExecutor;
    }

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

Étape 2: Ajouter une méthode qui exécute un processus asynchrone

@Service
public class ProcessServiceImpl implements ProcessService {

    private static final Logger logger = LoggerFactory.getLogger(ProcessServiceImpl.class);

    @Async("processExecutor")
    @Override
    public void process() {
        logger.info("Received request to process in ProcessServiceImpl.process()");
        try {
            Thread.sleep(15 * 1000);
            logger.info("Processing complete");
        }
        catch (InterruptedException ie) {
            logger.error("Error in ProcessServiceImpl.process(): {}", ie.getMessage());
        }
    }
}

Étape 3: Ajouter une API dans le contrôleur pour exécuter le traitement asynchrone

@Autowired
private ProcessService processService;

@RequestMapping(value = "ping/async", method = RequestMethod.GET)
    public ResponseEntity<Map<String, String>> async() {
        processService.process();
        Map<String, String> response = new HashMap<>();
        response.put("message", "Request is under process");
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

J'ai également écrit un blog et une application de travail sur GitHub avec ces étapes. Veuillez vérifier: http://softwaredevelopercentral.blogspot.com/2017/07/asynchronous-processing-async-in-spring.html

9
Aj Tech Developer

J'avais un problème similaire et j'avais les annotations @Async et @EnableAsync dans les beans corrects et la méthode s'exécutait toujours de manière synchrone. Après avoir vérifié les journaux, un avertissement m'avait indiqué que j'avais plusieurs beans de type ThreadPoolTaskExecutor et qu'aucun d'entre eux ne s'appelait taskExecutor. Donc ...

@Bean(name="taskExecutor")
public ThreadPoolTaskExecutor defaultTaskExecutor() {
     ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
     //Thread pool configuration
     //...
     return pool;
}

Voir http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html pour la configuration disponible pour le pool de threads.

3
Carlos Andres

Suivez les trois étapes:

1 étape: Utilisez @EnableAsync avec @configuration ou @SpringBootApplication

@EnableAsync public class Application {

2 étape:

/**
 * THIS FOR ASYNCRONOUS PROCESS/METHOD
 * @return
 */
@Bean
public Executor asyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(5);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("Anycronous Process-");
    executor.initialize();
    return executor;
}

3ème étape: Placez @Async sur la méthode souhaitée

T

0