How to make a async REST with Spring?

2020-05-17 08:36发布

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?

2条回答
该账号已被封号
2楼-- · 2020-05-17 09:13

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.

查看更多
狗以群分
3楼-- · 2020-05-17 09:27

I'm facing performance issues when triggering Async methods. The Asynchronous child threads start executing very late (around 20 to 30 seconds delay). I'm using ThreadPoolTaskExecutor() in my main SpringBoot application class. You can also try the same if you consider perfomance as a factor.

查看更多
登录 后发表回答