22 nov. 2021

Tâches asynchrones avec Spring Boot

Pour lancer des tâches asynchrones avec Spring Boot, il est souvent recommandé d'utiliser @Async, en définissant un ThreadPoolTaskExecutor sous la forme d'un bean.

// Definition du ThreadPoolTaskExecutor
@EnableAsync
public class AsyncConfiguration {
    
    @Bean(name = "someTaskExecutor")
    public Executor asyncExecutor() 
    {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(25);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("some-task-exec-");
        executor.initialize();
        return executor;
    }
}


// Service utilisant le ThreadPoolTaskExecutor
@Service
public class AsyncService {

	@Async("someTaskExecutor")
	public void doSomethingAsync() {
    	...
    }


Cependant cette pratique a quelques limitations (non exhaustives) :
  • Toute méthode @Async invoquée au sein de sa classe ne sera pas asynchrone
  • Nécessite de déclarer la méthode annotée @Async, publique

Voici une technique qui permet de tirer parti du ThreadPoolTaskExecutor et de l'asynchrone, sans passer par les annotations @Async. En effet, il peut arriver que l'on souhaite juste lancer un traitement dans un thread, sans formalités

// Definition du ThreadPoolTaskExecutor
@EnableAsync
public class AsyncConfiguration {
    
    @Bean(name = "someTaskExecutor")
    public Executor asyncExecutor() 
    {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(25);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("some-task-exec-");
        executor.initialize();
        return executor;
    }
}


// Service utilisant le ThreadPoolTaskExecutor
@Service
public class AsyncService {

	// Import the bean executor defined in AsyncConfiguration
	@Autowired
    @Qualifier("someTaskExecutor")
    private Executor executor;

	public void doSomethingWithSomethingAsync() {
    	// Do something non-async
        ...
        
        // Then do something async
        CompletableFuture.runAsync(() -> {
           // Something async
        }, this.executor);
    }

	//@Async("someTaskExecutor")
	//public void doSomethingAsync() {
    //	...
    //}

Voici les étapes : 
  • Définir le ThreadPoolTaskExecutor (ou autre) sous la forme d'un bean
  • L'importer dans la classe où l'on souhaite faire un traitement async, via l'annotation @Autowired
  • Utiliser CompletableFuture.runAsync/supplyAsync avec en paramètre l'executor, et la fonction à traiter en asynchrone