Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a async REST with Spring?

Tags:

I'm trying to make a small REST using Spring Boot. I've never used Spring and used Java a long time ago (Java 7)!

In the last 2 years I have used only Python and C# (but like I said, I already used Java).

So, now, I'm trying to make a REST using async methods, and checked several examples, but still, I don't understand very well the "correct way" to do this.

Looking at the following documentation: http://carlmartensen.com/completablefuture-deferredresult-async, Java 8 has CompletableFuture that I can use with Spring, so, I made the following code:

Service:

@Service public class UserService {   private UserRepository userRepository;    // dependency injection   // don't need Autowire here   // https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html   public UserService(UserRepository userRepository) {     this.userRepository = userRepository;   }    @Async   public CompletableFuture<User> findByEmail(String email) throws InterrupedException {     User user = userRepository.findByEmail(email);     return CompletableFuture.completedFuture(user);   } } 

Repository:

public interface UserRepository extends MongoRepository<User, String> {   @Async   findByEmail(String email); } 

RestController:

@RestController public class TestController {    private UserService userService;    public TestController(UserService userService) {     this.userService = userService;   }    @RequestMapping(value = "test")   public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {     return userService.findByEmail(email).thenApplyAsync(user -> {       return user;     })   }   } 

This code give me the expected output. Then, looking at another documentation (sorry, I lost the link), I see that Spring accept the following code (which give me the expected output too):

  @RequestMapping(value = "test")   public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {     return userService.findByEmail(email);   }   } 

Is there a difference between the two methods?

Then, looking at the following guide: https://spring.io/guides/gs/async-method/, there's a @EnableAsync annotation in SpringBootApplication class. If I include the @EnableAsync annotation and create a asyncExecutor Bean like the code from last link, my application don't return nothing on /test endpoint (only a 200 OK response, but with blank body).

So, my rest is async without the @EnableAsync annotation? And why when I use @EnableAsync, the response body is blank?

like image 227
Roberto Correia Avatar asked Aug 03 '17 01:08

Roberto Correia


1 Answers

The response body is blank because the @Async annotation is used at findEmail method of UserRepository class, it means that there is no data returned to the following sentence User user = userRepository.findByEmail(email); because findByEmail method is running on other different thread and will return null instead of a List object.

The @Async annotation is enabled when you declare @EnableAsync that is the reason why it only happens when you use @EnableAsync because it activates the @Async of findEmail method to run it on other thread.

The method return userService.findByEmail(email); will return a CompletableFuture object that is created from UserService class.

The difference with the second method call is that thenApplyAsync method will create a totally new CompletableFuture from the previous one that comes from userService.findByEmail(email) and will only return the user object that comes from the first CompletableFuture.

 return userService.findByEmail(email).thenApplyAsync(user -> {       return user;     }) 

If you want to get the expected results just remove the @Async annotation from findByEmail method, and finally add the @EnableAsync Annotation

If you need to clarify ideas of how to use Async methods, lets say that you have to call three methods and each one takes 2 seconds to finish, in a normal scenario you will call them method1, then method2 and finally method3 in that case you entire request will take 6 seconds. When you activate the Async approach then you can call three of them and just wait for 2 seconds instead of 6.

Add this long method to user service:

@Async public  CompletableFuture<Boolean> veryLongMethod()  {      try {         Thread.sleep(2000L);     } catch (InterruptedException e) {         e.printStackTrace();     }      return CompletableFuture.completedFuture(true); } 

And call it three times from Controller, like this

  @RequestMapping(value = "test")   public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {         CompletableFuture<Boolean> boolean1= siteService.veryLongMethod();         CompletableFuture<Boolean> boolean2= siteService.veryLongMethod();         CompletableFuture<Boolean> boolean3= siteService.veryLongMethod();          CompletableFuture.allOf(boolean1,boolean2,boolean3).join();     return userService.findByEmail(email);   }   

Finally measure the time that takes your response, if it takes more than 6 seconds then you are not running Async method, if it takes only 2 seconds then you succeed.

Also see the following documentation: @Async Annotation, Spring async methods, CompletableFuture class

Hope it help.

like image 146
Daniel C. Avatar answered Nov 11 '22 04:11

Daniel C.