Spring WebFlux предусматривает модель программирования на основе аннотаций, где компоненты @Controller и @RestController используют аннотации для выражения отображений запросов, ввода запросов, обработки исключений и многого другого. Аннотированные контроллеры имеют гибкие сигнатуры методов и не обязательно должны расширять базовые классы или реализовывать определенные интерфейсы.

В следующем листинге показан базовый пример:

Java
@RestController
public class HelloController {
@GetMapping("/hello")
public String handle() {
return "Hello WebFlux";
}
}
Kotlin
@RestController
class HelloController {
@GetMapping("/hello")
fun handle() = "Hello WebFlux"
}

В предыдущем примере метод возвращает String для записи в ответ.

@Controller

Вы можете определять бины контроллера, используя стандартное определение бинов Spring. Стереотип @Controller позволяет производить автоматическое обнаружение и согласуется с общими средствами поддержки Spring для обнаружения классов с аннотацией @Component в classpath и автоматической регистрации определений бинов для них. Он также работает как стереотип для аннотированного класса, указывая на его роль как веб-компонента.

Чтобы активировать автоматическое обнаружение таких бинов с аннотацией @Controller, вы можете добавить сканирование компонентов в конфигурацию Java, как показано в следующем примере:

Java
@Configuration
@ComponentScan("org.example.web") 
public class WebConfig {
// ...
}
  1. Сканируем пакет org.example.web.
Kotlin
@Configuration
@ComponentScan("org.example.web") 
class WebConfig {
// ...
}
  1. Сканируем пакет org.example.web.

@RestController — это составная аннотация, которая сама мета-аннотируется аннотациями @Controller и @ResponseBody, указывая на контроллер, каждый метод которого наследует аннотацию @ResponseBody на уровне типов и, следовательно, осуществляет запись непосредственно в тело ответа вместо распознавания представления и визуализации при помощи HTML-шаблона.

Отображение запросов

Аннотация @RequestMapping используется для отображения запросов на методы контроллеров. Она имеет различные атрибуты для сопоставления по URL-адресу, HTTP-методу, параметрам запроса, заголовкам и типам среды передачи данных. Вы можете использовать её на уровне класса для выражения общих отображений или на уровне метода для сужения до конкретного отображения конечной точки.

Существуют также специфические для HTTP-метода варианты сокращения аннотации @RequestMapping:

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

Предыдущие аннотации являются кастомными аннотациями, которые указываются потому, что, вероятно, большинство методов контроллера нужно будет отобразить на определенный HTTP-методом, а не использовать аннотацию @RequestMapping, которая по умолчанию сопоставляется со всеми HTTP-методами. В то же время, аннотация @RequestMapping по-прежнему необходима на уровне класса для выражения общих отображений.

В следующем примере используются отображения на уровне типов и методов:

Java
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
Kotlin
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
fun getPerson(@PathVariable id: Long): Person {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun add(@RequestBody person: Person) {
// ...
}
}

URI шаблоны

Вы можете отобразить запросы, используя стандартные маски (они же шаблон поиска (glob patterns)) и подстановочные знаки:

Шаблон Описание Пример

?

Выполняет сопоставление с одним символом

"/pages/t?st.html" соответствует "/pages/test.html" и "/pages/t3st.html"

*

Выполняет сопоставление с нулем или более символами в сегменте пути

"/resources/*.png" соответствует "/resources/file.png"

"/projects/*/versions" соответствует "/projects/spring/versions" но не соответствует "/projects/spring/boot/versions"

**

Выполняет сопоставление с нулем или более сегментами пути до конца пути

"/resources/**" соответствует "/resources/file.png" и "/resources/images/file.png"

"/resources/**/file.png" недействителен, так как ** допускается только в конце пути.

{name}

Выполняет сопоставление с сегментом пути и записывает его в переменную с именем "name".

"/projects/{project}/versions" соответствует "/projects/spring/versions" и захватывает project=spring

{name:[a-z]+}

Выполняет сопоставление с выражением "[a-z]+" как переменной пути с именем "name".

"/projects/{project:[a-z]+}/versions" соответствует "/projects/spring/versions" но не "/projects/spring1/versions"

{*path}

Выполняет сопоставление с нулем или более сегментами пути до конца пути и записывает его в переменную с именем "path".

"/resources/{*file}" соответствует "/resources/images/file.png" и захватывает file=/images/file.png

К захваченным URI-переменным можно получить доступ с помощью аннотации @PathVariable, как это показано в следующем примере:

Java
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
Kotlin
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
// ...
}

Можно объявлять URI-переменные на уровне классов и методов, как показано в следующем примере:

Java
@Controller
@RequestMapping("/owners/{ownerId}") 
public class OwnerController {
@GetMapping("/pets/{petId}") 
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
  1. Отображение URI-идентификатора на уровне классов.
  2. Отображение URI-идентификатора на уровне метода.
Kotlin
@Controller
@RequestMapping("/owners/{ownerId}") 
class OwnerController {
@GetMapping("/pets/{petId}") 
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
// ...
}
}
  1. Отображение URI на уровне классов.
  2. Отображение URI на уровне метода.

Переменные URI-идентификаторов преобразуются в соответствующий тип, иначе возникает ошибка TypeMismatchException. Простые типы (int, long, Date и так далее) поддерживаются по умолчанию, но вы можете зарегистрировать поддержку любого другого типа данных.

Можно явным образом присвоить имена URI-переменным (например, @PathVariable("customId")), но также можете и не указывать эти сведения, если имена одинаковы, а ваш код компилируется с использованием отладочной информации или с флагом компилятора -parameters на Java 8.

Синтаксис {*varName} объявляет URI-переменную, которая соответствует нулю или более оставшимся сегментам пути. Например, /resources/{*path} соответствует всем файлам в каталоге /resources/, а переменная "path" отражает полный путь в каталоге /resources.

Синтаксис {varName:regex} объявляет URI-переменную с регулярным выражением, которое имеет синтаксис: {varName:regex}. Например, при URL-адресе /spring-web-3.0.5.jar следующий метод извлекает имя, версию и расширение файла:

Java
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
// ...
}
Kotlin
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun handle(@PathVariable version: String, @PathVariable ext: String) {
// ...
}

Шаблоны путей URI-идентификатора также могут содержать встроенные плейсхолдеры ${…}, которые распознаются при запуске с помощью PropertySourcesPlaceholderConfigurer в локальных, системных источниках свойств, источниках свойств окружения и др. Можно использовать это, например, для параметризации базового URL-адреса на основе некоторой внешней конфигурации.

Spring WebFlux использует PathPattern и PathPatternParser для поддержки сопоставления путей URI-идентификаторов. Оба класса находятся в spring-web и специально разработаны для использования с HTTP-путями URL-адресов в веб-приложениях, где большое количество шаблонов URI-путей сопоставляется во время выполнения.

Spring WebFlux не поддерживает суффиксальное сопоставление с образом – в отличие от Spring MVC, где такое отображение, как /person, также соответствует /person.*. Для согласования содержимого на основе URL-адреса, если это необходимо, рекомендуем использовать параметр запроса, который является более простым, явным и менее уязвимым для эксплойтов на основе пути URL-адреса.

Сравнение шаблонов

Если несколько образцов совпадают с URL-адресом, их необходимо сравнить, чтобы найти наилучшее совпадение. Это делается с помощью PathPattern.SPECIFICITY_COMPARATOR, который ищет образцы, которые являются более конкретными.

Для каждого образца вычисляется оценка, основанная на количестве переменных URI-идентификатора и подстановочных знаков, где URI-переменная оценивается ниже, чем подстановочный знак. Побеждает образец с меньшим общим счетом. Если два образца имеют одинаковые показатели, выбирается более длинный.

Образцы catch-all (например, **, {*varName}) исключаются из подсчета и всегда сортируется последними. Если два образца являются одновременно catch-all, выбирается более длинный.

Типы потребляемых сред передачи данных

Вы можете сузить отображение запросов на основе Content-Type запроса, как показано в следующем примере:

Java
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
Kotlin
@PostMapping("/pets", consumes = ["application/json"])
fun addPet(@RequestBody pet: Pet) {
// ...
}

Атрибут "consumes" также поддерживает выражения отрицания - например, !text/plain означает любой тип содержимого, отличный от text/plain.

Вы можете объявить общий атрибут consumes на уровне класса. Однако, в отличие от большинства других атрибутов отображения запросов, при использовании на уровне класса, атрибут consumes на уровне метода скорее переопределяет, а не расширяет объявление на уровне класса.

MediaType предоставляет константы для часто используемых типов сред передачи данных, таких как APPLICATION_JSON_VALUE и APPLICATION_XML_VALUE.

Производимые типы сред передачи данных

Можно сузить диапазон отображения запросов на основе заголовка запроса Accept и списка типов содержимого, которые производит метод контроллера, как показано в следующем примере:

Java
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
Kotlin
@GetMapping("/pets/{petId}", produces = ["application/json"])
@ResponseBody
fun getPet(@PathVariable String petId): Pet {
// ...
}

Тип среды передачи данных может определять набор символов. Поддерживаются отрицаемые выражения - например, !text/plain означает любой тип содержимого, отличный от text/plain.

Можно объявлять общий атрибут produces на уровне класса. Однако, в отличие от большинства других атрибутов отображения запросов, при использовании на уровне класса атрибут produces на уровне метода скорее переопределяет, а не расширяет объявление на уровне класса.

MediaType предоставляет константы для часто используемых типов сред передачи данных, таких как, например, APPLICATION_JSON_VALUE и APPLICATION_XML_VALUE.

Параметры и заголовки

Можно сузить диапазон отображения запросов на основе условий параметров запроса. Можно проверить наличие параметра запроса myParam), его отсутствие (!myParam) или же наличие определенного значения (myParam=myValue). В следующих примерах проверяется наличие параметра со значением:

Java
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") 
public void findPet(@PathVariable String petId) {
// ...
}
  1. Убедитесь, что myParam равно myValue.
Kotlin
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) 
fun findPet(@PathVariable petId: String) {
// ...
}
  1. Убедитесь, что myParam равно myValue.

То же самое можно использовать и с условиями заголовка запроса, как показано в следующем примере:

Java
@GetMapping(path = "/pets", headers = "myHeader=myValue") 
public void findPet(@PathVariable String petId) {
// ...
}
  1. Убедитесь, что myHeader равен myValue.
Kotlin
@GetMapping("/pets", headers = ["myHeader=myValue"]) 
fun findPet(@PathVariable petId: String) {
// ...
}
  1. Убедитесь, что myHeader равен myValue.

HTTP-методы HEAD, OPTIONS

@GetMapping@RequestMapping(method=HttpMethod.GET)) прозрачно поддерживают HTTP-метод HEAD для отображения запросов. Методы контроллера не нужно менять. Функция-обёртка ответа, применяемая в HttpHandler, обеспечивает установку заголовка Content-Length на количество записанных байт (без фактической записи в ответ).

По умолчанию HTTP-метод OPTIONS обрабатывается путем установки заголовка ответа Allow в список HTTP-методов, перечисленных во всех методах, помеченных аннотацией @RequestMapping.

В случае аннотации @RequestMapping без объявления HTTP-методов, заголовок Allow устанавливается в GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS. Методы контроллера всегда должны объявлять поддерживаемые HTTP-методы (например, с помощью вариантов, специфичных для HTTP-методов: @GetMapping, @PostMapping и другие).

Вы можете явно отобразить метод, аннотированный @RequestMapping, на HTTP-метод HEAD и HTTP-метод OPTIONS, но в рядовом случае это делать не обязательно.

Кастомные аннотации

Spring WebFlux поддерживает использование составных аннотаций для отображения запросов. Это аннотации, которые сами являются мета-аннотациями @RequestMapping и составлены для повторного объявления подмножества (или всех) атрибутов, помеченных аннотацией @RequestMapping, с более узкой, более конкретной целью.

@GetMapping, @PostMapping, @PutMapping, @DeleteMapping и @PatchMapping являются примерами составных аннотаций. Они предусматриваются потому, что, вероятно, большинство методов контроллера нужно будет сопоставить с конкретным HTTP-методом вместо использования аннотации @RequestMapping, которая по умолчанию производит сопоставление со всеми HTTP-методами. Если вам требуется пример составных аннотаций, посмотрите, как они объявляются.

Spring WebFlux также поддерживает кастомные атрибуты отображения запросов с кастомно заданной логикой отображения запросов. Это более расширенный вариант, который требует создания подкласса RequestMappingHandlerMapping и переопределения метода getCustomMethodCondition, в котором можно проверить пользовательский атрибут и вернуть свое собственное RequestCondition.

Явная регистрация

Вы можете программно регистрировать методы обработчика, которые можно использовать для динамической регистрации или при более сложных случаях, например, если имеются разные экземпляры одного и того же обработчика под разными URL-адресами. В следующем примере показано, как это сделать:

Java
@Configuration
public class MyConfig {
@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) (1)
    throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
        .paths("/user/{id}").methods(RequestMethod.GET).build(); 
Method method = UserHandler.class.getMethod("getUser", Long.class); 
mapping.registerMapping(info, handler, method); 
}
}
  1. Внедряем целевой обработчик и отображение обработчика для контроллеров.
  2. Подготавливаем метаданные отображения запроса.
  3. Получаем метод обработчика.
  4. Добавляем регистрацию.
Kotlin
@Configuration
class MyConfig {
@Autowired
fun setHandlerMapping(mapping: RequestMappingHandlerMapping, handler: UserHandler) { 
val info = RequestMappingInfo.paths("/user/{id}").methods(RequestMethod.GET).build() 
val method = UserHandler::class.java.getMethod("getUser", Long::class.java) 
mapping.registerMapping(info, handler, method) 
}
}
  1. Внедряем целевой обработчик и отображение обработчика для контроллеров.
  2. Подготавливаем метаданные отображения запроса.
  3. Получаем метод обработчика.
  4. Добавляем регистрацию.

Методы обработчика

Методы обработчика с аннотацией @RequestMapping имеют гибкую сигнатуру и могут выбирать из ряда поддерживаемых аргументов и возвращаемых значений метода контроллера.

Аргументы метода

В следующей таблице приведены поддерживаемые аргументы метода контроллера.

Реактивные типы (Reactor, RxJava или другие) поддерживаются в аргументах, для разрешения которых требуется блокирующий ввод-вывод (например, чтение тела запроса). Это отмечено в колонке "Описание". Реактивные типы не ожидаются для аргументов, которые не требуют блокировки.

Аргумент java.util.Optional из JDK 1.8 поддерживается в качестве аргумента метода в сочетании с аннотациями, имеющими атрибут required (например, @RequestParam, @RequestHeader и другие), и эквивалентен required=false.

Аргумент метода контроллера Описание

ServerWebExchange

Обеспечивает доступ к полному ServerWebExchange – контейнер для запроса и ответа HTTP, атрибуты запроса и сессии, методы checkNotModified и др.

ServerHttpRequest, ServerHttpResponse

Обеспечивает доступ к запросу или ответу HTTP.

WebSession

Обеспечивает доступ к сессии. Это не приводит к началу новой сессии, если не добавлены атрибуты. Поддерживает реактивные типы.

java.security.Principal

Текущий аутентифицированный пользователь – возможно, конкретный класс реализации Principal, если он известен. Поддерживает реактивные типы.

org.springframework.http.HttpMethod

HTTP-метод запроса.

java.util.Locale

Текущие региональные настройки запроса, определяемые наиболее конкретным доступным LocaleResolver - по сути, сконфигурированным LocaleResolver или LocaleContextResolver.

java.util.TimeZone + java.time.ZoneId

Часовой пояс, связанный с текущим запросом, определенный LocaleContextResolver.

@PathVariable

Предназначен для обеспечения доступа к переменным шаблона URI-идентификаторов.

@MatrixVariable

Предназначен для обеспечения доступа к парам "имя-значение" в сегментах пути URI-идентификаторов.

@RequestParam

Обеспечивает доступ к параметрам запроса. Значения параметров преобразуются в объявленный тип аргументов метода.

Обратите внимание, что использовать аннотацию @RequestParam необязательно — например, для установки его атрибутов. См. раздел "Любой другой аргумент" далее в этой таблице.

@RequestHeader

Предназначен для обеспечения доступа к заголовкам запросов. Значения заголовков преобразуются в объявленный тип аргумента метода.

@CookieValue

Предназначен для обеспечения доступа к cookie. Значения cookie преобразуются в объявленный тип аргумента метода.

@RequestBody

Обеспечивает доступ к телу HTTP-запроса. Содержимое тела преобразуется в объявленный тип аргумента метода с помощью экземпляров HttpMessageReader. Поддерживает реактивные типы.

HttpEntity<B>

Предназначен для обеспечения доступа к заголовкам и телу запроса. Тело преобразуется с помощью экземпляров HttpMessageReader. Поддерживает реактивные типы.

@RequestPart

Обеспечивает доступа к компоненту в запросе multipart/form-data. Поддерживает реактивные типы.

java.util.Map, org.springframework.ui.Model, и org.springframework.ui.ModelMap.

Предназначены для обеспечения доступа к модели, которая используется в HTML-контроллерах и отображается в шаблонах как часть визуализации представления.

@ModelAttribute

Предназначен для оеспечения доступа к существующему атрибуту в модели (экземпляр которого создается, если он отсутствует) с привязкой данных и валидацией.

Обратите внимание, что использовать аннотацию @ModelAttribute необязательно - например, для установки его атрибутов. См. раздел "Любой другой аргумент" далее в этой таблице.

Errors, BindingResult

Обеспечивают доступ к ошибкам валидации и привязки данных для объекта команды, т.е. аргумента @ModelAttribute. Аргумент Errors или BindingResult нужно объявлять сразу после аргумента валидируемого метода.

SessionStatus + class-level @SessionAttributes

Предназначен для пометки завершения обработки формы, что вызывает очистку атрибутов сессии, объявленных через аннотацию @SessionAttributes на уровне класса. Более подробную информацию смотрите в разделе, посвященном аннотации @SessionAttributes.

UriComponentsBuilder

Предназначен для подготовки URL-адреса, связанного с хостом, портом, схемой и контекстным путем текущего запроса.

@SessionAttribute

Предназначен для обеспечения доступа к любому атрибуту сессии, в отличие от атрибутов модели, хранящихся в сессии в результате объявления аннотации @SessionAttributes на уровне класса.

@RequestAttribute

Предназначен для обеспечения доступа к атрибутам запроса.

Любой другой аргумент

Если аргумент метода не соответствует ни одному из вышеперечисленных, он по умолчанию разрешается как @RequestParam, если это простой тип, определяемый BeanUtils#isSimplePropert, или как @ModelAttribute, в противном случае.

Возвращаемые значения

В следующей таблице приведены поддерживаемые возвращаемые значения метода контроллера. Обратите внимание, что реактивные типы из таких библиотек, как Reactor, RxJava или других, обычно поддерживаются для всех возвращаемых значений.

Возвращаемое значение метода контроллера Описание

@ResponseBody

Возвращаемое значение кодируется через экземпляры HttpMessageWriter и записывается в ответ.

HttpEntity<B>, ResponseEntity<B>

Возвращаемое значение задает, что полный ответ, включая HTTP-заголовки, и тело будут кодироваться через экземпляры HttpMessageWriter и записываться в ответ.

HttpHeaders

Предназначено для возврата ответа с заголовками и без тела.

String

Имя представления, которое должно быть распознано с помощью экземпляров ViewResolver и использоваться вместе с неявной моделью – определяется через объекты команд и методы, помеченные аннотацией @ModelAttribute. Метод обработчика также может программно доработать модель, объявив аргумент Model.

View

Экземпляр View, предназначенный для визуализации вместе с неявной моделью - определяется через объекты команд и методы с аннотацией @ModelAttribute. Метод обработчика также может программно доработать модель, объявив аргумент Model.

java.util.Map, org.springframework.ui.Model

Атрибуты для добавления в неявную модель, при этом имя представления неявно определяется на основе пути запроса.

@ModelAttribute

Атрибут, добавляемый к модели, при этом имея представления неявно определяется на основе пути запроса.

Обратите внимание, что аннотация @ModelAttribute является необязательной. См. раздел "Любое другое возвращаемое значение" далее в этой таблице.

Rendering

API для сценариев визуализации моделей и представлений.

void

Считается, что метод с возвращаемым типом void, который, вероятно, является асинхронным (например, Mono<Void>) (или с возвращаемым значением null), полностью обработал ответ, если он также имеет аргумент ServerHttpResponse, ServerWebExchange или аннотацию @ResponseStatus. То же самое верно, если контроллер выполнил положительную проверку ETag или временной метки lastModified.

Если ни одно из вышеперечисленных значений не верно, возвращаемый тип void может также указывать на "отсутствие тела ответа" для REST-контроллеров или выбор имени представления по умолчанию для HTML-контроллеров.

Flux<ServerSentEvent>, Observable<ServerSentEvent>, или другой реактивный тип

Связан с генерацией событий, посылаемых сервером. Функцию-обёртку ServerSentEvent можно опустить, если необходимо записать только данные (однако, text/event-stream должен быть запрошен или объявлен в отображении через атрибут produces).

Любое другое возвращаемое значение

Если возвращаемое значение не соответствует ни одному из вышеперечисленных, оно по умолчанию обрабатывается как имя представления, если это String или void (применяется выбор имени представления по умолчанию), или как атрибут модели, который должен быть добавлен в модель, если только это не простой тип, как определено BeanUtils#isSimpleProperty, и в этом случае он остается неразрешенным.

Преобразование типов

Некоторые аннотированные аргументы метода контроллера, представляющие ввод запроса на основе строк (такие как @RequestParam, @RequestHeader, @PathVariable, @MatrixVariable и @CookieValue)), могут потребовать преобразовать тип, если аргумент объявлен не как String.

В таких случаях преобразование типов применяется автоматически на основе сконфигурированных преобразователей. По умолчанию поддерживаются простые типы (такие как int, long, Date и другие). Преобразование типов можно настроить с помощью WebDataBinder или путем регистрации Formatters с помощью FormattingConversionService.

Практическим вопросом при преобразовании типов является обработка пустого исходного значения строки. Такое значение рассматривается как отсутствующее, если оно становится null в результате преобразования типа. Это может быть характерно для Long, UUID и других целевых типов. Если вам нужно допустить внедрение null, то либо используйте флаг required в аннотации аргумента, либо объявите аргумент как @Nullable.

Матричные переменные

В RFC 3986 описаны пары "имя-значение" в сегментах пути. В Spring WebFlux мы называем их "матричными переменными", основываясь на "старом посте" Тима Бернерса-Ли, но их также можно назвать параметрами URI-пути.

Матричные переменные могут появляться в любом сегменте пути, при этом каждая переменная отделяется точкой с запятой, а несколько значений разделяются запятыми – например, "/cars;color=red,green;year=2012". Множественные значения также могут быть заданы через повторяющиеся имена переменных – например, color=red;color=green;color=blue).

В отличие от Spring MVC, в WebFlux наличие или отсутствие матричных переменных в URL-адресе не влияет на отображение запросов. Иными словами, вам не обязательно использовать URI-переменную для маскировки содержимого переменной. Тем не менее, если нужно получить доступ к матричным переменным из метода контроллера, то нужно добавить URI-переменную в сегмент пути, где ожидаются матричные переменные. В следующем примере показано, как это сделать:

Java
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
Kotlin
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
fun findPet(@PathVariable petId: String, @MatrixVariable q: Int) {
// petId == 42
// q == 11
}

Учитывая, что все сегменты пути могут содержать матричные переменные, иногда может потребоваться определить, в какой переменной пути должна находиться матричная переменная, как показано в следующем примере:

Java
// GET /owners/42;q=11/pets/21;q=22
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable(name="q", pathVar="ownerId") int q1,
@MatrixVariable(name="q", pathVar="petId") int q2) {
// q1 == 11
// q2 == 22
}
Kotlin
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
@MatrixVariable(name = "q", pathVar = "ownerId") q1: Int,
@MatrixVariable(name = "q", pathVar = "petId") q2: Int) {
// q1 == 11
// q2 == 22
}

Можно определить матричную переменную как необязательную и указать значение по умолчанию, как показано в следующем примере:

Java
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
Kotlin
// GET /pets/42
@GetMapping("/pets/{petId}")
fun findPet(@MatrixVariable(required = false, defaultValue = "1") q: Int) {
// q == 1
}

Чтобы получить все переменные матрицы, используйте MultiValueMap, как показано в следующем примере:

Java
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable MultiValueMap<String, String> matrixVars,
@MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {
// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 22, "s" : 23]
}
Kotlin
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
@MatrixVariable matrixVars: MultiValueMap<String, String>,
@MatrixVariable(pathVar="petId") petMatrixVars: MultiValueMap<String, String>) {
// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 22, "s" : 23]
}

@RequestParam

Можно использовать аннотацию @RequestParam для привязки параметров запроса к аргументу метода в контроллере. Следующий фрагмент кода демонстрирует использование:

Java
@Controller
@RequestMapping("/pets")
public class EditPetForm {
// ...
@GetMapping
public String setupForm(@RequestParam("petId") int petId, Model model) { 
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
  1. Использование аннотации @RequestParam.
Kotlin
import org.springframework.ui.set
@Controller
@RequestMapping("/pets")
class EditPetForm {
// ...
@GetMapping
fun setupForm(@RequestParam("petId") petId: Int, model: Model): String { 
val pet = clinic.loadPet(petId)
model["pet"] = pet
return "petForm"
}
// ...
}
  1. Использование аннотации @RequestParam.
Понятие "параметр запроса" в Servlet API объединяет параметры запроса, данные формы и многокомпонентные данные в одно целое. Однако в WebFlux доступ к каждому из них осуществляется по отдельности через ServerWebExchange. Хотя аннотация @RequestParam привязывается только к параметрам запроса, можно использовать привязку данных для применения параметров запроса, данных формы и многокомпонентных элементов к объекту команды.

Параметры метода, использующие аннотацию @RequestParam, по умолчанию являются обязательными, но также можно задать, чтоб параметр метода не являлся обязательным, установив соответствующий флаг для @RequestParam в false или объявив аргумент с помощью обертки java.util.Optional.

Если тип параметра целевого метода не является String, преобразование типов применяется автоматически.

Если аннотация @RequestParam объявлена для аргумента Map<String, String> или MultiValueMap<String, String>, Map заполняется всеми параметрами запроса.

Обратите внимание, что использовать аннотацию @RequestParam необязательно - например, для установки его атрибутов. По умолчанию, любой аргумент, который является простым типом значения (как определено BeanUtils#isSimpleProperty) и не разрешен никаким другим распознавателем аргументов, обрабатывается так, как если бы он был аннотирован @RequestParam.

@RequestHeader

Вы можете использовать аннотацию @RequestHeader для привязки заголовка запроса к аргументу метода в контроллере.

В следующем примере показан запрос с заголовками:

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

Код из следующего примера позволяет получить значение заголовков Accept-Encoding и Keep-Alive:

Java
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding, (1)
@RequestHeader("Keep-Alive") long keepAlive) { 
//...
}
  1. Получаем значения заголовка Accept-Encoding.
  2. Получаем значения заголовка Keep-Alive.
Kotlin
@GetMapping("/demo")
fun handle(
@RequestHeader("Accept-Encoding") encoding: String, (1)
@RequestHeader("Keep-Alive") keepAlive: Long) { 
//...
}
  1. Получаем значения заголовка Accept-Encoding.
  2. Получаем значения заголовка Keep-Alive.

Если тип параметра целевого метода не является String, преобразование типов применяется автоматически.

Если аннотация @RequestHeader используется в аргументе Map<String, String>, MultiValueMap<String, String> или HttpHeaders, Map заполняется всеми значениями заголовков.

Имеется встроенная поддержка для преобразования строки, разделенной запятой, в массив или коллекцию строк или других типов, известных системе преобразования типов. Например, параметр метода, аннотированный @RequestHeader("Accept"), может иметь тип String, а также String[] или List<String>.

@CookieValue

Вы можете использовать аннотацию @CookieValue для привязки значения HTTP cookie к аргументу метода в контроллере.

В следующем примере показан запрос с файлом cookie:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

В следующем примере кода показано, как получить значение файла cookie:

Java
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { 
//...
}
  1. Получение значения cookie.
Kotlin
@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) { 
//...
}
  1. Получение значения cookie.

Если тип параметра целевого метода не является String, преобразование типов применяется автоматически.

@ModelAttribute

Вы можете использовать аннотацию @ModelAttribute на аргументе метода для доступа к атрибуту из модели или создания его экземпляра, если он отсутствует. На атрибут модели также накладываются значения параметров запроса и полей формы, названия которых совпадают с названиями полей. Это называется связыванием данных, и оно избавляет от необходимости парсить и преобразовывать отдельные параметры запроса и поля формы. В следующем примере показано, как привязывается экземпляр Pet:

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { } 
  1. Привязываем экземпляр Pet.
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String { } 
  1. Привязываем экземпляр Pet.

Экземпляр Pet в предыдущем примере разрешается следующим образом:

  • Из модели, если она уже добавлена через Model.

  • Из HTTP-сессии через аннотацию @SessionAttributes.

  • Из вызова конструктора по умолчанию.

  • Из вызова "главного конструктора" с аргументами, соответствующими параметрам запроса или полям формы. Имена аргументов определяются через аннотацию @ConstructorProperties для JavaBeans или через сохраняемые во время выполнения имена параметров в байт-коде.

После получения экземпляра атрибута модели применяется привязка данных. Класс WebExchangeDataBinder сопоставляет имена параметров запроса и полей формы с именами полей целевого Object. Соответствующие поля заполняются после преобразования типов, где это необходимо.

Привязка данных может приводить к ошибкам. По умолчанию генерируется WebExchangeBindException, но для проверки таких ошибок в методе контроллера можно добавить аргумент BindingResult непосредственно рядом с аннотацией @ModelAttribute, как показано в следующем примере:

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { 
if (result.hasErrors()) {
return "petForm";
}
// ...
}
  1. Добавление BindingResult.
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { 
if (result.hasErrors()) {
return "petForm"
}
// ...
}
  1. Добавление BindingResult.

Можно автоматически применять валидацию после привязки данных, добавив аннотацию javax.validation.Valid или аннотацию @Validated из Spring. В следующем примере используется аннотация @Valid:

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { 
if (result.hasErrors()) {
return "petForm";
}
// ...
}
  1. Использование аннотации @Valid для аргумента атрибута модели.
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { 
if (result.hasErrors()) {
return "petForm"
}
// ...
}
  1. Использование аннотации @Valid для аргумента атрибута модели.

Spring WebFlux, в отличие от Spring MVC, поддерживает реактивные типы в модели – например, Mono<Account> или io.reactivex.Single<Account>. Вы можете объявить аргумент, помеченный аннотацией @ModelAttribute, с функцией-обёрткой реактивного типа или без нее, после чего он будет разрешен соответствующим образом, в фактическое значение, если это необходимо. Однако обратите внимание, что для использования аргумента BindingResult перед ним необходимо объявить аргумент с аннотацией @ModelAttribute без функции-обёртки реактивного типа, как было показано ранее. Кроме того, можно обрабатывать любые ошибки через реактивный тип, как это показано в следующем примере:

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public Mono<String> processSubmit(@Valid @ModelAttribute("pet") Mono<Pet> petMono) {
return petMono
.flatMap(pet -> {
    // ...
})
.onErrorResume(ex -> {
    // ...
});
}
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") petMono: Mono<Pet>): Mono<String> {
return petMono
    .flatMap { pet ->
        // ...
    }
    .onErrorResume{ ex ->
        // ...
    }
}

Обратите внимание, что использовать аннотацию @ModelAttribute необязательно - например, для установки его атрибутов. По умолчанию, любой аргумент, который не является простым типом значения (как определено BeanUtils#isSimpleProperty) и не разрешается никаким другим распознавателем аргументов, обрабатывается так, как если бы он был аннотирован @ModelAttribute.

@SessionAttributes

@SessionAttributes используется для хранения атрибутов модели в WebSession между запросами. Это аннотация на уровне типа, которая объявляет атрибуты сессии, используемые конкретным контроллером. Здесь обычно перечисляются имена атрибутов модели или типы атрибутов модели, которые должны прозрачно храниться в сессии для последующих запросов на доступ.

Рассмотрим следующий пример:

Java
@Controller
@SessionAttributes("pet") 
public class EditPetForm {
// ...
}
  1. Использование аннотации @SessionAttributes.
Kotlin
@Controller
@SessionAttributes("pet") 
class EditPetForm {
// ...
}
  1. Использование аннотации @SessionAttributes.

При первом запросе, если в модель добавляется атрибут модели с именем pet, он автоматически продвигается в WebSession и сохраняется в ней. Он остается там до тех пор, пока другой метод контроллера не использует аргумент метода SessionStatus для очистки хранилища, как показано в следующем примере:

Java
@Controller
@SessionAttributes("pet") 
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) { 
if (errors.hasErrors()) {
    // ...
}
    status.setComplete();
    // ...
}
}
}
  1. Использование аннотации @SessionAttributes.
  2. Использование переменной SessionStatus.
Kotlin
@Controller
@SessionAttributes("pet") 
class EditPetForm {
// ...
@PostMapping("/pets/{id}")
fun handle(pet: Pet, errors: BindingResult, status: SessionStatus): String { 
if (errors.hasErrors()) {
    // ...
}
status.setComplete()
// ...
}
}
  1. Использование аннотации @SessionAttributes.
  2. Использование переменной SessionStatus.

@SessionAttribute

Если требуется доступ к заранее существующим атрибутам сессии, которые управляются глобально (то есть вне контроллера - например, фильтром) и могут присутствовать или отсутствовать, то можно использовать аннотацию @SessionAttribute для параметра метода, как показано в следующем примере:

Java
@GetMapping("/")
public String handle(@SessionAttribute User user) { 
// ...
}
  1. Использование аннотации @SessionAttribute.
Kotlin
@GetMapping("/")
fun handle(@SessionAttribute user: User): String { 
// ...
}
  1. Использование аннотации @SessionAttribute.

Для случаев использования, требующих добавления или удаления атрибутов сессии, обратите внимание на возможность внедрения WebSession в метод контроллера.

Для временного хранения атрибутов модели в сессии как части рабочего процесса контроллера, рассмотрите возможность использования SessionAttributes.

@RequestAttribute

Аналогично аннотации @SessionAttribute, можно использовать аннотацию @RequestAttribute для осуществления доступа к уже существующим атрибутам запроса, созданным ранее (например, WebFilter), как показано в следующем примере:

Java
@GetMapping("/")
public String handle(@RequestAttribute Client client) { 
// ...
}
  1. Использование аннотации @RequestAttribute.
Kotlin
@GetMapping("/")
fun handle(@RequestAttribute client: Client): String { 
// ...
}
  1. Использование аннотации @RequestAttribute.

Многокомпонентное содержимое

ServerWebExchange обеспечивает доступ к многокомпонентному содержимому. Лучший способ обработки формы загрузки файла (например, из браузера) в контроллере – это привязка данных к объекту команды, как показано в следующем примере:

Java
class MyForm {
private String name;
private MultipartFile file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
// ...
}
}
Kotlin
class MyForm(
val name: String,
val file: MultipartFile)
@Controller
class FileUploadController {
@PostMapping("/form")
fun handleFormUpload(form: MyForm, errors: BindingResult): String {
// ...
}
}

Вы также можете отправлять многокомпонентные запросы от небраузерных клиентов в сценарии с использованием RESTful служб. В следующем примере используется файл вместе с JSON:

POST /someUrl
Content-Type: multipart/mixed
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit
{
"name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

Можно получить доступ к отдельным компонентам с помощью аннотации @RequestPart, как показано в следующем примере:

Java
@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata, (1)
@RequestPart("file-data") FilePart file) { 
// ...
}
  1. Использование аннотации @RequestPart для получения метаданных.
  2. Использование аннотации @RequestPart для получения файла.
Kotlin
@PostMapping("/")
fun handle(@RequestPart("meta-data") Part metadata, (1)
@RequestPart("file-data") FilePart file): String { 
// ...
}
  1. Использование аннотации @RequestPart для получения метаданных.
  2. Использование аннотации @RequestPart для получения файла.

Чтобы десериализовать сырое содержимое компонента (например, в JSON – аналогично аннотации @RequestBody), можно объявить конкретный целевой Object вместо Part, как показано в следующем примере:

Java
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) { 
// ...
}
  1. Использование аннотации @RequestPart для получения метаданных.
Kotlin
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData): String { 
// ...
}
  1. Использование аннотации @RequestPart для получения метаданных.

Можно использова аннотацию @RequestPart в сочетании с аннотацией javax.validation.Valid или аннотацией @Validated из Spring, что приведет к применению стандартной Bean Validation. Ошибки валидации приводят к возникновению WebExchangeBindException, в результате чего выдается ответ 400 (BAD_REQUEST). Исключение содержит BindingResult с деталями ошибки и также может быть обработано в методе контроллера путем объявления аргумента с асинхронной функцией-обёрткой и последующего использования операторов, связанных с ошибкой:

Java
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
// используем один из операторов onError*...
}
Kotlin
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
// ...
}

Чтобы получить доступ ко всем многокомпонентным данным в виде MultiValueMap, вы можете использовать аннотацию @RequestBody, как это показано в следующем примере:

Java
@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) { 
// ...
}
  1. Использование аннотации @RequestBody.
Kotlin
@PostMapping("/")
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { 
// ...
}
  1. Использование аннотации @RequestBody.

Для последовательного потокового доступа к многокомпонентным данным можно использовать аннотацию @RequestBody с Flux<Part> (или Flow<Part> в Kotlin), как показано в следующем примере:

Java
@PostMapping("/")
public String handle(@RequestBody Flux<Part> parts) { 
// ...
}
  1. Использование аннотации @RequestBody.
Kotlin
@PostMapping("/")
fun handle(@RequestBody parts: Flow<Part>): String { 
// ...
}
  1. Использование аннотации @RequestBody.

@RequestBody

Аннотацию @RequestBody можно использовать, чтобы прочитать тело запроса и десериализовать его в объект Object HttpMessageReader. В следующем примере используется аргумент, аннотированный @RequestBody:

Java
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
Kotlin
@PostMapping("/accounts")
fun handle(@RequestBody account: Account) {
// ...
}

В отличие от Spring MVC, в WebFlux аргумент метода, помеченного аннотацией @RequestBody, поддерживает реактивные типы и полностью неблокирующее чтение и (клиент-серверную) потоковую передачу.

Java
@PostMapping("/accounts")
public void handle(@RequestBody Mono<Account> account) {
// ...
}
Kotlin
@PostMapping("/accounts")
fun handle(@RequestBody accounts: Flow<Account>) {
// ...
}

Вы можете использовать опцию кодеков сообщений, передаваемых по протоколу HTTP, в конфигурации WebFlux для конфигурирования или кастомной настройки устройств чтения сообщений.

Аннотацию @RequestBody можно применять в сочетании с аннотацией javax.validation.Valid или аннотацией @Validated из Spring, что приводит к применению стандартной Bean Validation. Ошибки валидации вызывают WebExchangeBindException, что приводит к ответу 400 (BAD_REQUEST). Исключение содержит BindingResult с деталями ошибки и также может быть обработано в методе контроллера путем объявления аргумента с асинхронной функцией-обёрткой и последующего использования операторов, связанных с ошибкой:

Java
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Mono<Account> account) {
// используем один из операторов onError*...
}
Kotlin
@PostMapping("/accounts")
fun handle(@Valid @RequestBody account: Mono<Account>) {
// ...
}

HttpEntity

HttpEntity более или менее идентичен аннотации @RequestBody по части использования, но основывается на объекте-контейнере, который открывает заголовки и тело запроса. В следующем примере используется HttpEntity:

Java
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
Kotlin
@PostMapping("/accounts")
fun handle(entity: HttpEntity<Account>) {
// ...
}

@ResponseBody

Аннотацию @ResponseBody можно использовать нам методе, чтобы возврат сериализовался в тело ответа через HttpMessageWriter. В следующем примере показано, как это сделать:

Java
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
Kotlin
@GetMapping("/accounts/{id}")
@ResponseBody
fun handle(): Account {
// ...
}

@ResponseBody также поддерживается на уровне класса, в этом случае она наследуется всеми методами контроллера. В этом заключается действие аннотации @RestController, которая является ничем иным, как мета-аннотацией, помеченной аннотациями @Controller и @ResponseBody .

@ResponseBody поддерживает реактивные типы, что означает, что вы можете возвращать типы Reactor или RxJava и получать асинхронные значения, которые они создают, в ответ.

Вы можете комбинировать методы, помеченные аннотацией @ResponseBody, с представлениями сериализации формата JSON.

Можно использовать опцию кодеков сообщений, передаваемых по протоколу HTTP, в конфигурации WebFlux для конфигурирования или кастомной настройки устройств чтения сообщений.

ResponseEntity

Атрибут ResponseEntity подобен аннотации @ResponseBody, но со статусом и заголовками. Например:

Java
@GetMapping("/something")
public ResponseEntity<String> handle() {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).body(body);
}
Kotlin
@GetMapping("/something")
fun handle(): ResponseEntity<String> {
val body: String = ...
val etag: String = ...
return ResponseEntity.ok().eTag(etag).build(body)
}

WebFlux поддерживает использование реактивного типа с одним значением для асинхронного создания ResponseEntity и/или реактивных типов с одним и несколькими значениями для тела. Это позволяет использовать различные асинхронные ответы с ResponseEntity следующим образом:

  • ResponseEntity<Mono<T>> или ResponseEntity<Flux<T>> немедленно сообщают статус и заголовки ответа, в то время как тело ответа предоставляется асинхронно в более поздний момент. Используем Mono, если тело состоит из 0..1 значений, или Flux, если оно может создавать несколько значений.

  • Mono<ResponseEntity<T>> предусматривает все три параметра - статус ответа, заголовки и тело - асинхронно в более поздний момент. Это позволяет изменять статус ответа и заголовки в зависимости от результатов асинхронной обработки запроса.

  • Mono<ResponseEntity<Mono<T>>> или Mono<ResponseEntity<Flux<T>>> являются еще одной возможной, хотя и менее распространенной альтернативой. Сначала они асинхронно предоставляют статус ответа и заголовки, а затем, во вторую очередь, тело ответа, также асинхронно.

Jackson JSON

Spring предусматривает поддержку библиотеки Jackson JSON.

Представления JSON

Spring WebFlux предусматривает встроенные средства поддержки представлений сериализации из библиотеки Jackson, которая позволяет визуализировать исключительно подмножество всех полей Object. Чтобы использовать его с методами контроллера, аннотированными @ResponseBody, или классом ResponseEntity, можно задействовать аннотацию @JsonView из Jackson для активации класса представления сериализации, как показано в следующем примере:

Java
@RestController
public class UserController {
@GetMapping("/user")
@JsonView(User.WithoutPasswordView.class)
public User getUser() {
return new User("eric", "7!jd#h23");
}
}
public class User {
public interface WithoutPasswordView {};
public interface WithPasswordView extends WithoutPasswordView {};
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
@JsonView(WithoutPasswordView.class)
public String getUsername() {
return this.username;
}
@JsonView(WithPasswordView.class)
public String getPassword() {
return this.password;
}
}
Kotlin
@RestController
class UserController {
@GetMapping("/user")
@JsonView(User.WithoutPasswordView::class)
fun getUser(): User {
return User("eric", "7!jd#h23")
}
}
class User(
@JsonView(WithoutPasswordView::class) val username: String,
@JsonView(WithPasswordView::class) val password: String
) {
interface WithoutPasswordView
interface WithPasswordView : WithoutPasswordView
}
Аннотация @JsonView позволяет использовать массив классов представлений, но можно задать только одно на каждый метод контроллера. Используйте составной интерфейс, если вам нужно активировать несколько представлений.

Model

Вы можете использовать аннотацию @ModelAttribute:

  • В качестве аргумента метода в методах с аннотацией @RequestMapping для создания или обеспечения доступа к объекту из модели и привязки его к запросу через WebDataBinder.

  • В качестве аннотации на уровне метода в классах, помеченных аннотациями @Controller или @ControllerAdvice, помогающей инициализировать модель перед вызовом метода с аннотацией @RequestMapping.

  • На методе с аннотацией @RequestMapping, чтобы пометить его возвращаемое значение как атрибут модели.

В этом разделе описаны методы, аннотированные @ModelAttribute, или второй пункт из предыдущего списка. Контроллер может иметь любое количество методов, помеченных аннотацией @ModelAttribute. Все такие методы вызываются перед методами, помеченными аннотацией @RequestMapping в том же контроллере. Метод с аннотацией @ModelAttribute также можно разделить между контроллерами через аннотацию @ControllerAdvice.

Методы с аннотацией @ModelAttribute имеют гибкие сигнатуры методов. Они поддерживают многие из тех же аргументов, что и методы, помеченные аннотацией @RequestMapping, за исключением самой аннотации @ModelAttribute или всего, что связано с телом запроса.

В следующем примере используется метод, помеченный аннотацией @ModelAttribute:

Java
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}
Kotlin
@ModelAttribute
fun populateModel(@RequestParam number: String, model: Model) {
model.addAttribute(accountRepository.findAccount(number))
// add more ...
}

В следующем примере добавлен только один атрибут:

Java
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}
Kotlin
@ModelAttribute
fun addAccount(@RequestParam number: String): Account {
return accountRepository.findAccount(number);
}
Если имя не задано явным образом, то имя по умолчанию выбирается на основе типа, как это описано в javadoc по соглашениям. Всегда можно присвоить явное имя с помощью перегруженного метода addAttribute или через атрибут name в аннотации @ModelAttribute (для возвращаемого значения).

Spring WebFlux, в отличие от Spring MVC, явно поддерживает реактивные типы в модели (например, Mono<Account> или io.reactivex.Single<Account>). Такие асинхронные атрибуты модели можно прозрачно разрешить (и обновить модель) в их фактические значения во время вызова @RequestMapping, если аргумент с аннотацией @ModelAttribute объявлен без функции-обёртки, как показано в следующем примере:

Java
@ModelAttribute
public void addAccount(@RequestParam String number) {
Mono<Account> accountMono = accountRepository.findAccount(number);
model.addAttribute("account", accountMono);
}
@PostMapping("/accounts")
public String handle(@ModelAttribute Account account, BindingResult errors) {
// ...
}
Kotlin
import org.springframework.ui.set
@ModelAttribute
fun addAccount(@RequestParam number: String) {
val accountMono: Mono<Account> = accountRepository.findAccount(number)
model["account"] = accountMono
}
@PostMapping("/accounts")
fun handle(@ModelAttribute account: Account, errors: BindingResult): String {
// ...
}

Кроме того, любые атрибуты модели, имеющие функцию-обёртку реактивного типа, разрешаются в их фактические значения (а модель обновляется) непосредственно перед визуализацией представления.

Также можно использовать аннотацию @ModelAttribute в качестве аннотации на уровне метода для методов, помеченных аннотацией @RequestMapping, и в этом случае возвращаемое значение метода, помеченного аннотацией @RequestMapping, интерпретируется как атрибут модели. Обычно делать этого не требуется, поскольку такая логика работы задана по умолчанию в HTML-контроллерах, если только возвращаемое значение не является String, которая в противном случае будет интерпретирована как имя представления. Аннотация @ModelAttribute также может помочь кастомно настроить имя атрибута модели, как показано в следующем примере:

Java
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
// ...
return account;
}
Kotlin
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
fun handle(): Account {
// ...
return account
}

DataBinder

Классы с аннотацией @Controller или @ControllerAdvice могут располагать методами, аннотированными @InitBinder, чтобы инициализировать экземпляры WebDataBinder. Те, в свою очередь, используются для:

  • Привязки параметров запроса (то есть данных формы или запроса) к объекту модели.

  • Преобразования значений запроса на основе String(таких как параметры запроса, переменные пути, заголовки, cookie и др.) в целевой тип аргументов метода контроллера.

  • Форматирования значений объектов модели как String значений при визуализации HTML-форм.

Методы с аннотацией @InitBinder могут регистрировать специфичные для контроллера компоненты java.beans.PropertyEditor или Converter и Formatter из Spring. Кроме того, можно использовать Java-конфигурацию WebFlux для регистрации типов Converter и Formatter в совместно используемом на глобальном уровне FormattingConversionService.

Методы, помеченные аннотацией @InitBinder, поддерживают многие из тех же аргументов, что и методы с аннотацией @RequestMapping, за исключением аргументов, аннотированных @ModelAttribute (объект команды). Как правило, они объявляются с аргументом WebDataBinder для регистрации и возвращаемым значением void. В следующем примере используется аннотация @InitBinder:

Java
@Controller
public class FormController {
@InitBinder 
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
  1. Использование аннотации @InitBinder.
Kotlin
@Controller
class FormController {
@InitBinder 
fun initBinder(binder: WebDataBinder) {
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
dateFormat.isLenient = false
binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
}
// ...
}

Как вариант, при использовании конфигурации на основе Formatter через общий FormattingConversionService можно повторно использовать тот же подход и зарегистрировать экземпляры Formatter, специфичные для контроллера, как показано в следующем примере:

Java
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd")); 
}
// ...
}
  1. Добавление пользовательского форматтера (в данном случае DateFormatter).
Kotlin
@Controller
class FormController {
@InitBinder
fun initBinder(binder: WebDataBinder) {
binder.addCustomFormatter(DateFormatter("yyyy-MM-dd")) 
}
// ...
}
  1. Добавление пользовательского форматтера (в данном случае DateFormatter).

Структура модели

В контексте веб-приложений привязывание данных подразумевает привязку параметров HTTP-запроса (то есть данных формы или параметров запроса) к свойствам объекта модели и его вложенных объектов.

Для привязки данных открыты только public свойства, соответствующие соглашениям об именовании JavaBeans – например, методы public String getFirstName() и public void setFirstName(String) для свойства firstName.

Объект модели и его вложенный граф объектов также иногда называют объектом команды, базовым объектом формы или POJO ("старый добрый Java-объект").

По умолчанию Spring разрешает привязку ко всем публичным свойствам в графе объектов модели. Это означает, что необходимо тщательно продумывать, какие публичные свойства есть у модели, поскольку клиент может сделать целью любое публичное свойство, даже те, которые не предполагаются для данного случая использования.

Например, при наличии конечной точки данных HTTP-формы вредоносный клиент может передать значения для свойств, которые существуют в графе объектов модели, но не являются частью HTML-формы, представленной в браузере. Это может привести к тому, что в объект модели и любой из его вложенных объектов будут установлены данные, обновление которых не ожидается.

Рекомендуемый подход заключается в использовании специализированного объекта модели, который открывает только те свойства, которые имеют отношение к отправке формы. Например, в форме для изменения адреса электронной почты пользователя объект модели должен объявить минимальный набор свойств, как в следующем ChangeEmailForm.

public class ChangeEmailForm {
private String oldEmailAddress;
private String newEmailAddress;
public void setOldEmailAddress(String oldEmailAddress) {
this.oldEmailAddress = oldEmailAddress;
}
public String getOldEmailAddress() {
return this.oldEmailAddress;
}
public void setNewEmailAddress(String newEmailAddress) {
this.newEmailAddress = newEmailAddress;
}
public String getNewEmailAddress() {
return this.newEmailAddress;
}
}

Если у вас нет возможности или вы не желаете использовать специализированный объект модели для каждого случая использования привязки данных, то должны ограничить свойства, которые разрешены для привязки данных. В идеале этого можно достичь, зарегистрировав шаблоны допустимых полей с помощью метода setAllowedFields() в WebDataBinder.

Например, чтобы зарегистрировать шаблоны допустимых полей в вашем приложении, можно реализовать метод, помеченный аннотацией @InitBinder, в компоненте с аннотацией @Controller или @ControllerAdvice, как показано ниже:

@Controller
public class ChangeEmailController {
@InitBinder
void initBinder(WebDataBinder binder) {
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
}
// @RequestMapping methods, etc.
}

В дополнение к регистрации шаблонов допустимых полей, можно также зарегистрировать шаблоны недопустимых полей с помощью метода setDisallowedFields() в DataBinder и его подклассах. Однако обратите внимание, что "допускающий список" безопаснее, чем "запрещающий список". Следовательно, использование setAllowedFields() следует предпочесть вместо использования setDisallowedFields().

Обратите внимание, что сопоставление с шаблонами допустимых полей чувствительно к регистру; в то время как сопоставление с шаблонами недопустимых полей – нет. Кроме того, поле, соответствующее шаблону недопустимых полей, не будет принято, даже если оно также соответствует шаблону в списке допустимых.

Очень важно правильно сконфигурировать шаблоны допустимых и недопустимых полей при непосредственном открытии модели предметной области для привязки данных. В противном случае это будет большой риск для безопасности.

Кроме того, настоятельно рекомендуется не использовать типы из вашей модели предметной области, такие как сущности JPA или Hibernate, в качестве объекта модели в сценариях привязки данных.

Управление исключениями

Классы с аннотацией @Controller и @ControllerAdvice могут располагать методами, помеченными аннотацией @ExceptionHandler, для обработки исключений из методов контроллера. Следующий пример содержит такой метод обработчика:

Java
@Controller
public class SimpleController {
// ...
@ExceptionHandler 
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
  1. Объявление аннотации @ExceptionHandler.
Kotlin
@Controller
class SimpleController {
// ...
@ExceptionHandler 
fun handle(ex: IOException): ResponseEntity<String> {
// ...
}
}
  1. Объявление аннотации @ExceptionHandler.

Исключение может совпадать с распространяемым высокоуровневым исключением (например, прямо генерируемое IOException ) или с вложенной причиной внутри исключения-обёртки (например, IOException, обернутое внутри IllegalStateException).

Для совпадающих типов исключений предпочтительно объявлять целевое исключение в качестве аргумента метода, как показано в предыдущем примере. Кроме того, объявление аннотации может сужать типы исключений, которые должны совпадать. Обычно мы рекомендуем быть как можно более конкретными в сигнатуре аргумента и объявлять главные отображения корневых исключений на аннотацию @ControllerAdvice в соответствующем порядке.

Метод с аннотацией @ExceptionHandler в WebFlux поддерживает те же аргументы метода и возвращаемые значения, что и метод с аннотацией @RequestMapping, за исключением аргументов метода, связанных с телом запроса и аннотацией @ModelAttribute.

Поддержка методов, аннотированных @ExceptionHandler, в Spring WebFlux обеспечивается HandlerAdapter для методов с аннотацией @RequestMapping.

Работа с исключениями в REST API

Общим требованием для служб на основе REST является включение информации об ошибке в тело ответа. Spring Framework не осуществляет это автоматически, поскольку указание информации об ошибке в теле ответа зависит от конкретного приложения. Однако аннотация @RestController может использовать методы, помеченные аннотацией @ExceptionHandler, с возвращаемым значением ResponseEntity для установки статуса и тела ответа. Такие методы также могут быть объявлены в классах с аннотацией @ControllerAdvice, чтобы применять их на глобальном уровне.

Обратите внимание, что Spring WebFlux не имеет эквивалента для ResponseEntityExceptionHandler из Spring MVC, поскольку WebFlux вызывает только ResponseStatusException (или их подклассы), и их не нужно преобразовывать в код состояния HTTP.

Advice контроллера

Обычно методы, помеченные аннотациями @ExceptionHandler, @InitBinder и @ModelAttribute, применяются в пределах класса @Controller (или иерархии классов), в котором они объявлены. Если вам требуется, чтобы такие методы применялись более глобально (для всех контроллеров), можно объявить их в классе, аннотированном @ControllerAdvice или @RestControllerAdvice.

Аннотация @ControllerAdvice аннотирована при помощи @Component, что означает, что такие классы можно регистрировать как бины Spring через сканирование компонентов. Аннотация @RestControllerAdvice – это составная аннотация, которая помечена и аннотацией @ControllerAdvice, и аннотацией @ResponseBody, что означает, что методы с аннотацией @ExceptionHandler визуализируются в теле ответа через преобразование сообщений (в отличие от разрешения представлений или визуализации шаблона).

При запуске классы инфраструктуры для методов, аннотированных @RequestMapping и @ExceptionHandler, обнаруживают бины Spring, аннотированные @ControllerAdvice, и затем применяют их методы во время выполнения. Глобальные методы, аннотированные при помощи @ExceptionHandler (из аннотации @ControllerAdvice) применяются после локальных (из аннотации @Controller). Напротив, глобальные методы с аннотациями @ModelAttribute и @InitBinder применяются перед локальными.

По умолчанию методы, помеченные аннотацией @ControllerAdvice, применяются к каждому запросу (то есть ко всем контроллерам), но можно сузить круг контроллеров, используя атрибуты аннотации, как показано в следующем примере:

Java
// Выбираем целью все контроллеры, аннотированные при помощи @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Выбираем целью все контроллеры в определенных пакетах
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Выбираем целью все контроллеры, назначаемые определенным классам
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
Kotlin
// Выбираем целью все контроллеры, аннотированные при помощи @RestController
@ControllerAdvice(annotations = [RestController::class])
public class ExampleAdvice1 {}
// Выбираем целью все контроллеры в определенных пакетах
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Выбираем целью все контроллеры, назначаемые определенным классам
@ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class])
public class ExampleAdvice3 {}

Селекторы в предыдущем примере вычисляются во время выполнения и могут негативно повлиять на производительность при широком использовании. Более подробную информацию смотрите в javadoc по аннотации @ControllerAdvice.