В этой статье мы рассмотрим определение нескольких функций маршрутизатора для различных логических доменов в Spring WebFlux. Это не может быть проблемой, если вы создаете «Микросервисы», так как вы, скорее всего, будете работать только в одном домене для каждой службы, но если вы этого не сделаете, вам, вероятно, потребуется включить в ваше приложение несколько доменов, которые пользователи или ваши собственные сервисы могут взаимодействовать с. Код для этого настолько прост, насколько я надеялся, и может быть объяснен в нескольких предложениях. Чтобы сделать этот пост немного более интересным, мы рассмотрим код Spring, который делает все это возможным.
Если вы новичок в WebFlux, я рекомендую взглянуть на мою предыдущую статью [Работа с Spring WebFlux] ( https://lankydanblog.com/2018/03/15/doing-stuff-with-spring-webflux/ ), где я написал несколько подробных примеров и объяснений по этому вопросу.
Итак, давайте сначала установим сцену. У вас есть два разных домена в вашем приложении, скажем люди и местоположения. Возможно, вы захотите держать их отделенными друг от друга не только логически, но и внутри вашего кода. Для этого вам нужен способ определения ваших маршрутов отдельно от каждого другого домена. Вот что мы рассмотрим в этом посте.
Если вы думаете, что уже знаете ответ на эту проблему, то вы, вероятно, правы. Это действительно так просто. Давайте работать до этого, хотя. Чтобы создать маршруты только для домена пользователей, создайте компонент RouterFunction
который отображается на соответствующие функции-обработчики, как RouterFunction
ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Configuration public class MyRouter { // works for a single bean @Bean public RouterFunction<ServerResponse> routes(PersonHandler personHandler) { return RouterFunctions.route(GET( "/people/{id}" ).and(accept(APPLICATION_JSON)), personHandler::get) .andRoute(GET( "/people" ).and(accept(APPLICATION_JSON)), personHandler::all) .andRoute(POST( "/people" ).and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post) .andRoute(PUT( "/people/{id}" ).and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put) .andRoute(DELETE( "/people/{id}" ), personHandler::delete) .andRoute(GET( "/people/country/{country}" ).and(accept(APPLICATION_JSON)), personHandler::getByCountry); } } |
Это создает маршруты к различным функциям-обработчикам в PersonHandler
.
Итак, теперь мы хотим добавить маршруты для логики местоположения. Мы могли бы просто добавить маршруты к этому бину, как показано ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@Configuration public class MyRouter { // not ideal! @Bean public RouterFunction<ServerResponse> routes(PersonHandler personHandler, LocationHandler locationHandler) { return RouterFunctions.route(GET( "/people/{id}" ).and(accept(APPLICATION_JSON)), personHandler::get) .andRoute(GET( "/people" ).and(accept(APPLICATION_JSON)), personHandler::all) .andRoute(POST( "/people" ).and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post) .andRoute(PUT( "/people/{id}" ).and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put) .andRoute(DELETE( "/people/{id}" ), personHandler::delete) .andRoute(GET( "/people/country/{country}" ).and(accept(APPLICATION_JSON)), personHandler::getByCountry) .andRoute(GET( "/locations/{id}" ).and(accept(APPLICATION_JSON)), locationHandler::get); } } |
Теперь компонент содержит ссылку на LocationHandler
поэтому маршрут местоположения можно настроить. Проблема с этим решением состоит в том, что он требует, чтобы код был связан вместе. Более того, если вам нужно добавить еще больше обработчиков, вы скоро будете перегружены количеством зависимостей, вводимых в этот bean-компонент.
Способ обойти это — создать несколько RouterFunction
. Вот и все. Таким образом, если мы создадим один в домене людей, скажем PersonRouter
и один в домене местоположений с именем LocationRouter
, каждый из них может определить нужные им маршруты, а Spring сделает все остальное. Это работает, потому что Spring проходит через контекст приложения, находит или создает любые компоненты RouterFunction
и объединяет их в одну функцию для последующего использования.
Используя эту информацию, мы можем написать код ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Configuration public class PersonRouter { // solution @Bean public RouterFunction<ServerResponse> peopleRoutes(PersonHandler personHandler) { return RouterFunctions.route(GET( "/people/{id}" ).and(accept(APPLICATION_JSON)), personHandler::get) .andRoute(GET( "/people" ).and(accept(APPLICATION_JSON)), personHandler::all) .andRoute(POST( "/people" ).and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post) .andRoute(PUT( "/people/{id}" ).and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put) .andRoute(DELETE( "/people/{id}" ), personHandler::delete) .andRoute(GET( "/people/country/{country}" ).and(accept(APPLICATION_JSON)), personHandler::getByCountry); } } |
и
1
2
3
4
5
6
7
8
|
@Configuration public class LocationRouter { // solution @Bean public RouterFunction<ServerResponse> locationRoutes(LocationHandler locationHandler) { return RouterFunctions.route(GET( "/locations/{id}" ).and(accept(APPLICATION_JSON)), locationHandler::get); } } |
PersonRouter
может храниться с кодом другого человека / человека, и LocationRouter
может делать то же самое.
Чтобы сделать это более интересным, почему это работает?
RouterFunctionMapping
— это класс, который извлекает все bean- RouterFunction
созданные в контексте приложения. Компонент RouterFunctionMapping
создается в WebFluxConfigurationSupport
который является эпицентром для конфигурации Spring WebFlux. @EnableWebFlux
аннотацию @EnableWebFlux
в класс конфигурации или полагаясь на автоконфигурацию, запускается цепочка событий, и одним из них является сбор всех наших RouterFunction
.
Ниже представлен класс RouterFunctionMapping
. Я удалил его конструкторы и несколько методов, чтобы сделать фрагмент здесь немного легче для восприятия.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean { @Nullable private RouterFunction<?> routerFunction; private List<HttpMessageReader<?>> messageReaders = Collections.emptyList(); // constructors // getRouterFunction // setMessageReaders @Override public void afterPropertiesSet() throws Exception { if (CollectionUtils.isEmpty( this .messageReaders)) { ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create(); this .messageReaders = codecConfigurer.getReaders(); } if ( this .routerFunction == null ) { initRouterFunctions(); } } /** * Initialized the router functions by detecting them in the application context. */ protected void initRouterFunctions() { if (logger.isDebugEnabled()) { logger.debug( "Looking for router functions in application context: " + getApplicationContext()); } List<RouterFunction<?>> routerFunctions = routerFunctions(); if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) { routerFunctions.forEach(routerFunction -> logger.info( "Mapped " + routerFunction)); } this .routerFunction = routerFunctions.stream() .reduce(RouterFunction::andOther) .orElse( null ); } private List<RouterFunction<?>> routerFunctions() { SortedRouterFunctionsContainer container = new SortedRouterFunctionsContainer(); obtainApplicationContext().getAutowireCapableBeanFactory().autowireBean(container); return CollectionUtils.isEmpty(container.routerFunctions) ? Collections.emptyList() : container.routerFunctions; } // getHandlerInternal private static class SortedRouterFunctionsContainer { @Nullable private List<RouterFunction<?>> routerFunctions; @Autowired (required = false ) public void setRouterFunctions(List<RouterFunction<?>> routerFunctions) { this .routerFunctions = routerFunctions; } } } |
Путь к извлечению всех маршрутов начинается в afterPropertiesSet
который вызывается после RouterFunctionMapping
компонента RouterFunctionMapping
. Поскольку его внутренняя RouterFunction
имеет значение null
она вызывает initRouterFunctions
вызывая серию методов, ведущих к выполнению routerFunctions
. Создается новый SortedRouterFunctionsContainer
(частный статический класс), устанавливающий поле routerFunctions
путем внедрения всех RouterFunction
из контекста приложения. Это работает, так как Spring вводит все bean-компоненты типа T
при вводе List<T>
. Теперь извлеченные функции RouterFunction
объединяются в единую RouterFunction
которая теперь используется для маршрутизации всех входящих запросов к соответствующему обработчику.
Это все, что нужно сделать. В заключение, определение нескольких RouterFunction
для разных бизнес-доменов очень просто, поскольку вы просто создаете их в любой области, в которой они наиболее целесообразны, и Spring отключается и извлекает их все. Чтобы раскрыть некоторые чудеса, мы RouterFunctionMapping
чтобы увидеть, как RouterFunction
нами функции RouterFunction
собираются и объединяются, чтобы их можно было использовать для маршрутизации запросов к обработчикам. В качестве заключительного замечания я понимаю, что этот пост в некоторых отношениях довольно тривиален, но иногда кажущаяся очевидной информация может быть довольно полезной.
Если вы еще этого не сделали, я рекомендую посмотреть мой предыдущий пост [Работа с Spring WebFlux] ( https://lankydanblog.com/2018/03/15/doing-stuff-with-spring-webflux/ ).
Наконец, если вы нашли этот пост полезным и хотели бы следить за моими новыми постами, пока я пишу их, вы можете подписаться на меня в Twitter по адресу @LankyDanDev .
Опубликовано на Java Code Geeks с разрешения Дэна Ньютона, партнера нашей программы JCG . См. Оригинальную статью здесь: Создание нескольких функций RouterFunction в Spring WebFlux
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |