Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot @Async method in controller is executing synchronously

My [basic] Spring Boot application accepts a request from the browser, sent via jQuery.get() and is supposed to immediately receive a response - such as "your request has been queued". To accomplish this, I wrote a controller:

@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.";   } } 

The DoWorkServiceImpl class implements a DoWorkService interface and is really simple. It has a single method to perform a time consuming task. I don't need anything returned from this service call, as an email will be delivered at the end of the work, both for failure or success scenarios. So it would effectively look like:

@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());     }   } } 

I thought this would work, but the browser's Ajax request waited for 10 seconds before returning the response. So the controller mapped method is calling the internal method annotated with @Async synchronously, it would seem. In a traditional Spring application, I typically add this to the XML configuration:

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

So I thought writing the equivalent of this in the main application class would help:

@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);   } } 

This did not change the behavior. The Ajax response still arrives after 10 seconds of sending the request. What am I missing?

The Spring Boot application can be downloaded here. With Maven installed, the project can be run with the simple command:

mvn clean spring-boot:run 

Note The issue was resolved thanks to the answer provided by @Dave Syer below, who pointed out that I was missing @EnableAsync in my application, even though I had the line in the code snippet above.

like image 796
Web User Avatar asked Mar 26 '15 16:03

Web User


People also ask

Is Spring boot synchronous or asynchronous?

Spring Boot uses a SimpleAsyncTaskExector to run an async method. This Executor runs by default and it can be overridden at two levels- at the individual method levels or at the application level.

How does @async work in Spring?

Simply put, annotating a method of a bean with @Async will make it execute in a separate thread. In other words, the caller will not wait for the completion of the called method. One interesting aspect in Spring is that the event support in the framework also has support for async processing if necessary.

What does @async annotation do in Spring boot?

The @EnableAsync annotation switches on Spring's ability to run @Async methods in a background thread pool. This class also customizes the Executor by defining a new bean. Here, the method is named taskExecutor , since this is the specific method name for which Spring searches.


1 Answers

You are calling the @Async method from another method in the same class. Unless you enable AspectJ proxy mode for the @EnableAsync (and provide a weaver of course) that won't work (google "proxy self-invocation"). The easiest fix is to put the @Async method in another @Bean.

like image 179
Dave Syer Avatar answered Sep 28 '22 23:09

Dave Syer