Как сделать асинхронный REST с помощью Spring?
Я пытаюсь сделать небольшой REST, используя Spring Boot. Я никогда не использовал Spring и не использовал Java давным-давно (Java 7)!
За последние 2 года я использовал только Python и С# (но, как я уже сказал, я уже использовал Java).
Итак, сейчас я пытаюсь создать REST с использованием асинхронных методов и проверил несколько примеров, но все же я не очень хорошо понимаю "правильный способ" сделать это.
Глядя на следующую документацию: http://carlmartensen.com/completablefuture-deferredresult-async, Java 8 имеет CompletableFuture
который я могу использовать с Spring, поэтому я сделал следующий код:
Служба:
@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);
}
}
Репозиторий:
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;
})
}
}
Этот код дает мне ожидаемый результат. Затем, просматривая другую документацию (извините, я потерял ссылку), я вижу, что Spring принимает следующий код (который также дает мне ожидаемый результат):
@RequestMapping(value = "test")
public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {
return userService.findByEmail(email);
}
}
Есть ли разница между этими двумя методами?
Затем, глядя на следующее руководство: https://spring.io/guides/gs/async-method/, есть аннотация @EnableAsync
в классе SpringBootApplication
. Если я @EnableAsync
аннотацию @EnableAsync
и создаю asyncExecutor
подобный коду из последней ссылки, мое приложение не возвращает ничего в конечной точке /test
(только ответ 200 OK, но с пустым телом).
Итак, мой отдых асинхронный без аннотации @EnableAsync
? И почему, когда я использую @EnableAsync
, тело ответа пустое?
Ответы
Ответ 1
Тело ответа пустое, потому что аннотация @Async
используется в методе findEmail класса UserRepository, это означает, что данные, возвращаемые в следующем предложении, отсутствуют. User user = userRepository.findByEmail(email);
потому что метод findByEmail работает в другом потоке и возвращает нуль вместо объекта List.
Аннотация @Async
включается, когда вы объявляете @EnableAsync
, поэтому это происходит только при использовании @EnableAsync
потому что он активирует метод @Async для findEmail для запуска его в другом потоке.
Метод return userService.findByEmail(email);
вернет объект CompletableFuture
, созданный из класса UserService
.
Разница со вторым вызовом метода заключается в том, что метод thenApplyAsync
создаст совершенно новый CompletableFuture
из предыдущего, который поступает из userService.findByEmail(email)
и будет возвращать только пользовательский объект, полученный из первого CompletableFuture
.
return userService.findByEmail(email).thenApplyAsync(user -> {
return user;
})
Если вы хотите, чтобы получить ожидаемые результаты просто удалить @Async
аннотации от метода findByEmail, и, наконец, добавить @EnableAsync
Аннотация
Если вам нужно уточнить идеи использования асинхронных методов, допустим, что вам нужно вызвать три метода, и каждый из них занимает 2 секунды, чтобы завершиться, в обычном сценарии вы будете называть их method1, затем method2 и, наконец, method3, в этом случае вы Весь запрос займет 6 секунд. Когда вы активируете асинхронный подход, вы можете вызвать три из них и просто подождать 2 секунды вместо 6.
Добавьте этот длинный метод в службу пользователя:
@Async
public CompletableFuture<Boolean> veryLongMethod() {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return CompletableFuture.completedFuture(true);
}
И трижды позвони из контроллера, вот так
@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);
}
Наконец, измерьте время, которое занимает ваш ответ, если это занимает более 6 секунд, то вы не используете асинхронный метод, если это займет всего 2 секунды, то у вас все получится.
Также см. Следующую документацию: аннотация @Async, асинхронные методы Spring, класс CompletableFuture
Надеюсь, это поможет.
Ответ 2
Я сталкиваюсь с проблемами производительности при запуске методов Async. Асинхронные дочерние потоки начинают выполнять очень поздно (от 20 до 30 секунд). Я использую ThreadPoolTaskExecutor() в моем основном классе приложений SpringBoot. Вы также можете попробовать то же самое, если вы считаете, что производительность является фактором.