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.
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.
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.
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With