Issue
I'm trying to implement a very simple asynchronous method call in my SpringBoot application but, whatever I do, I just cannot get it to work. I've looked at dozens of tutorials but they all seem unnecessarily complicated for what I'm doing. I'd appreciate any help you could provide.
Essentially, when a certain REST endpoint of my SpringBoot app is hit, it kicks off a slow task. Instead of making the client wait for the slow task to finish before getting a response, I want to asynchronously call the task method, and return a response to the client right away. For simplicity, the response that I want to return right away is generic -- I don't care about returning the result of the slow task itself.
I'm using Java 8 and SpringBoot 2.1.7
Initially, I started with just 2 classes:
1) Class 1:
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
2) Class 2:
@RestController
@RequestMapping("/rest")
public class Controller {
@RequestMapping(value = "/slowTask", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<?> slowTask() {
try{
asyncSlowTask();
// Want below response to be immediate -- instead, only occurs after above function is done.
return new ResponseEntity<String>("Accepted request to do slow task", HttpStatus.ACCEPTED);
}
catch (Exception e) {
return new ResponseEntity<String>("Exception", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Async
public void asyncSlowTask(){
// Sleep for 10s
Thread.sleep(10000);
}
}
I tested this by hitting the app locally: curl http://localhost:8080/rest/slowTask
What I expected is that the curl
command would return right away -- instead, it only returned after 10 seconds (after my slow task was done).
At this point, I read that the asynchronous method shouldn't be 'self-invoked' -- I had no idea if that's what I was doing but, to be safe, I moved the async method to a new class, so that my classes now looked like this:
1) Class 1:
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
2) Class 2:
@RestController
@RequestMapping("/rest")
public class Controller {
@RequestMapping(value = "/slowTask", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<?> slowTask() {
try{
AsyncClass.asyncSlowTask();
// Want below response to be immediate -- instead, only occurs after above function is done.
return new ResponseEntity<String>("Accepted request to do slow task", HttpStatus.ACCEPTED);
}
catch (Exception e) {
return new ResponseEntity<String>("Exception", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
3) Class 3:
public class AsyncClass {
@Async
public static void asyncSlowTask(){
// Sleep for 10s
Thread.sleep(10000);
}
}
Unfortunately, this didn't make any difference -- the request is still returned only after my slow task is completed.
I've also tried a bunch of other minor variations (including putting @EnableSync
and @Configuration
on every class), but none of them worked.
So, here are my questions:
1) Did I need to move the async method to that 3rd class, or was my original, two-class setup good enough?
2) Which class should have the @EnableSync
annotation?
I'm honestly completely lost as to where it should go -- all I know is that it should go in "one of your @Configuration classes", which means nothing to me.
3) Is there anything else I'm doing wrong??
Again, appreciate any help you can provide!
Solution
From the Spring Doc,
The default advice mode for processing @Async annotations is proxy which allows for interception of calls through the proxy only. Local calls within the same class cannot get intercepted that way
.
Any feature(async, scheduler, transactional, etc.,) which depends on spring proxy works only on the object managed by spring container.
Your code should look like this,
@RestController
@RequestMapping("/rest")
public class Controller {
private final AsyncClass asyncClass;
public Controller(final AsyncClass asyncClass) {
this.asyncClass = asyncClass;
}
@RequestMapping(value = "/slowTask", method = RequestMethod.GET)
public ResponseEntity<?> slowTask() {
try {
asyncClass.asyncSlowTask();
return new ResponseEntity<>("Accepted request to do slow task", HttpStatus.ACCEPTED);
} catch (final RuntimeException e) {
return new ResponseEntity<>("Exception", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
@Component
public class AsyncClass {
@Async
@SneakyThrows
public void asyncSlowTask() {
Thread.sleep(10000);
}
}
Which class should have the @EnableSync annotation?
You can have the annotation on the Application
class or any @Configuration
class which should be fine.
Answered By - Ramachandran Murugaian
Answer Checked By - Mary Flores (JavaFixing Volunteer)