Spring MVC уже некоторое время поддерживает поток асинхронной обработки запросов, и эта поддержка внутренне использует асинхронную поддержку Servlet 3 для таких контейнеров, как Tomcat / Jetty.
Поддержка Spring Web Async
Рассмотрим вызов службы, который занимает немного времени для обработки, имитируемого с задержкой:
1
2
3
4
5
6
7
8
|
public CompletableFuture<Message> getAMessageFuture() { return CompletableFuture.supplyAsync(() -> { logger.info( "Start: Executing slow task in Service 1" ); Util.delay( 1000 ); logger.info( "End: Executing slow task in Service 1" ); return new Message( "data 1" ); }, futureExecutor); } |
Если бы я вызывал этот сервис в потоке пользовательских запросов, традиционный поток контроллера блокировки был бы похож на это:
1
2
3
4
|
@RequestMapping ( "/getAMessageFutureBlocking" ) public Message getAMessageFutureBlocking() throws Exception { return service1.getAMessageFuture().get(); } |
Лучшим подходом является использование асинхронной поддержки Spring для возврата результата пользователю, когда он доступен из CompletableFuture, таким образом, не задерживая поток контейнеров:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@RequestMapping ( "/getAMessageFutureAsync" ) public DeferredResult<Message> getAMessageFutureAsync() { DeferredResult<Message> deffered = new DeferredResult<>( 90000 ); CompletableFuture<Message> f = this .service1.getAMessageFuture(); f.whenComplete((res, ex) -> { if (ex != null ) { deffered.setErrorResult(ex); } else { deffered.setResult(res); } }); return deffered; } |
Использование наблюдаемого в асинхронном потоке
Теперь перейдем к теме этой статьи. В последнее время я использовал превосходный тип Observable в Rx-java в качестве типов, возвращаемых службой, и хотел убедиться, что веб-слой также остается асинхронным при обработке типа Observable, возвращаемого из вызова службы.
Рассмотрим сервис, который был описан выше, теперь модифицированный, чтобы возвращать Observable:
1
2
3
4
5
6
7
8
9
|
public Observable<Message> getAMessageObs() { return Observable.<Message>create(s -> { logger.info( "Start: Executing slow task in Service 1" ); Util.delay( 1000 ); s.onNext( new Message( "data 1" )); logger.info( "End: Executing slow task in Service 1" ); s.onCompleted(); }).subscribeOn(Schedulers.from(customObservableExecutor)); } |
Я могу свести на нет все преимущества возврата Observable, заканчивая блокирующим вызовом на веб-слое. Наивный вызов будет следующим:
1
2
3
4
|
@RequestMapping ( "/getAMessageObsBlocking" ) public Message getAMessageObsBlocking() { return service1.getAMessageObs().toBlocking().first(); } |
Чтобы сделать этот поток асинхронным через веб-слой, лучшим способом обработки этого вызова является следующий, по сути, путем преобразования Observable в тип Spring DeferredResult:
1
2
3
4
5
6
7
|
@RequestMapping ( "/getAMessageObsAsync" ) public DeferredResult<Message> getAMessageAsync() { Observable<Message> o = this .service1.getAMessageObs(); DeferredResult<Message> deffered = new DeferredResult<>( 90000 ); o.subscribe(m -> deffered.setResult(m), e -> deffered.setErrorResult(e)); return deffered; } |
Это гарантировало бы, что поток, обрабатывающий поток пользователя, вернется, как только вызов службы завершится, и ответ пользователя будет обработан реактивно, как только наблюдаемое начнет излучать значения.
Если вы заинтересованы в дальнейшем изучении этого, вот репозиторий Github с рабочими образцами: https://github.com/bijukunjummen/spring-web-observable.
Рекомендации:
Справочное руководство Spring по асинхронным потокам на веб-уровне: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-async
Подробнее о Spring DeferredResult от неподражаемого Томаша Нуркевича в блоге NoBlogDefFound — http://www.nurkiewicz.com/2013/03/deferredresult-asynchronous-processing.html
Ссылка: | Использование rx-java Observable в потоке Spring MVC от нашего партнера по JCG Биджу Кунджуммена из блога all and sundry. |