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.
Solution 1:
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
.
Solution 2:
For all those who are still looking for all the steps in @Asnyc explained in a simple way, here is the answer:
Here is a simple example with @Async. Follow these steps to get @Async to work in your Spring Boot application:
Step 1: Add @EnableAsync annotation and Add TaskExecutor Bean to Application Class.
Example:
@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);
}
}
Step 2: Add Method which executes an Asynchronous Process
@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());
}
}
}
Step 3: Add an API in the Controller to execute the asynchronous processing
@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);
}
I have also written a blog and a working application on GitHub with these steps. Please check: http://softwaredevelopercentral.blogspot.com/2017/07/asynchronous-processing-async-in-spring.html
Solution 3:
I had a similar issue and I had the annotations @Async and @EnableAsync in the correct beans and still the method was executing synchronously. After I checked the logs there was a warning saying that I had more than one bean of type ThreadPoolTaskExecutor and none of them called taskExecutor So...
@Bean(name="taskExecutor")
public ThreadPoolTaskExecutor defaultTaskExecutor() {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
//Thread pool configuration
//...
return pool;
}
See http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html for the configuration available for the thread pool.