Spring WebFlux передбачає модель програмування на основі анотацій, де компоненти @Controller
та @RestController
використовують анотації для вираження відображень запитів, введення запитів, обробки винятків та багато чого іншого.
Анотовані контролери мають гнучкі сигнатури методів і не обов'язково повинні розширювати базові класи або
реалізовувати певні інтерфейси. У наступному лістингу показаний базовий приклад:
@RestController
public classHelloController {
@GetMapping("/hello")
public String handle (){
return "Hello WebFlux";
}
}
@RestController
class HelloController {
@GetMapping("/hello")
fun handle() = "Hello WebFlux"
}
У попередньому прикладі метод повертає String
для запису у відповідь.
@Controller
Ти можеш визначати біни контролера, використовуючи стандартне визначення бінів
Spring. Стереотип @Controller
дозволяє робити автоматичне виявлення та узгоджується із загальними
засобами підтримки Spring для виявлення класів з анотацією @Component
у classpath та автоматичної
реєстрації визначень бінів для них. Він також працює як стереотип для анотованого класу, вказуючи на його роль як
вебкомпонента.
Щоб активувати автоматичне виявлення таких бінів з анотацією @Controller
, можна
додати сканування компонентів до конфігурації Java, як показано в наступному прикладі:
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {
// ...
}
- Скануємо пакет
org.example .web
.
@Configuration
@ComponentScan("org.example.web")
class WebConfig {
// ...
}
- Скануємо пакет
org.example.web
.
@RestController
— це складена анотація, яка сама мета-анотується анотаціями
@Controller
та @ResponseBody
, вказуючи на контролер, кожен метод якого успадковує анотацію
@ResponseBody
на рівні типів і, отже, здійснює запис безпосередньо в тіло відповіді замість
розпізнавання подання та візуалізації за допомогою HTML-шаблону.
Відображення запитів
Анотація
@RequestMapping
використовується для відображення запитів на методи контролерів. Вона має різні
атрибути для зіставлення за URL-адресою, HTTP-методом, параметрами запиту, заголовками та типами середовища передачі
даних. Ти можеш використовувати її на рівні класу для вираження загальних відображень або на рівні методу для
звуження до конкретного відображення кінцевої точки. Існують також специфічні для HTTP-методу варіанти скорочення
анотації @RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
Попередні анотації є кастомними анотаціями, які вказуються
тому, що, ймовірно, більшість методів контролера потрібно буде відобразити на визначений HTTP-метод, а не
використовувати анотацію @RequestMapping
, яка за замовчуванням зіставляється з усіма HTTP-методами. У
той же час, анотація @RequestMapping
, як і раніше, необхідна на рівні класу для вираження загальних
відображень.
У наступному прикладі використовуються відображення на рівні типів та методів:
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus( HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
@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)) і знаки підстановки:
Шаблон | Опис | Приклад |
---|---|---|
|
Виконує зіставлення з одним символом |
|
|
Виконує зіставлення з нулем або більше символами в сегменті шляху |
|
|
Виконує зіставлення з нулем або більше сегментами шляху до кінця шляху |
|
|
Виконує зіставлення з сегментом шляху і записує його до змінної з ім'ям "name". |
|
|
Виконує зіставлення з виразом |
|
|
Виконує зіставлення з нулем або більше сегментами шляху до кінця шляху і записує його до змінної з ім'ям "path". |
|
До захоплених URI-змінних можна отримати доступ за допомогою анотації
@PathVariable
, як це показано в наступному прикладі:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(@PathVariable ownerId: Long , @PathVariable petId: Long): Pet {
// ...
}
Можна оголошувати URI-змінні на рівні класів і методів, як показано в наступному прикладі:
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
- Відображення URI-ідентифікатора на рівні класів.
- Відображення URI-ідентифікатор на рівні методу.
@Controller
@RequestMapping("/owners/{ownerId}")
class OwnerController {
@GetMapping("/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
// ...
}
}
- Відображення URI на рівні класів.
- Відображення 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
наступний метод отримує ім'я, версію та
розширення файлу:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\ \.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
// ...
}
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun handle(@PathVariable version: String, @PathVariable ext: String) {
// ... }
Шаблони шляхів URI-ідентифікатора також можуть містити вбудовані плейсхолдери ${…}
, які
розпізнаються під час запуску за допомогою PropertySourcesPlaceholderConfigurer
у локальних, системних
джерелах властивостей, джерелах властивостей оточення тощо. Можна використовувати це, наприклад, для параметризації
базової URL-адреси на основі певної зовнішньої конфігурації.
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
запиту, як показано в наступному прикладі:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun handle(@PathVariable version: String, @PathVariable ext: String) {
// ... }
@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
та списку типів вмісту, які виробляє метод контролера, як показано в наступному
прикладі:
@GetMapping(path = "/pets/{petId}", produces = "application/json" )
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
@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
). У наступних прикладах перевіряється наявність параметра зі значенням:
@GetMapping(path = "/pets/{petId} ", params = "myParam=myValue")
public void findPet (@PathVariable String petId) {
// ...
}
- Переконайся, що
myParam
дорівнюєmyValue
.
@GetMapping("/pets/{petId}", params = ["myParam=myValue"])
fun findPet(@PathVariable petId: String) {
// ...
}
- Переконайся, що
myParam
дорівнюєmyValue
.
Те саме можна використовувати і з умовами заголовка запиту, як показано в наступному прикладі:
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet( String petId){
// ...
}
- Переконайся, що
myHeader
дорівнюєmyValue
.
@GetMapping("/pets", headers = ["myHeader=myValue"])
fun findPet(@PathVariable petId: String) {
// ... }
- Переконайся, що
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-адресами. У цьому прикладі показано, як це зробити:
@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);
}
}
- Впроваджуємо цільовий обробник та відображення обробника для контролерів.
- Підготовлюємо метадані відображення запиту.
- Отримуємо метод обробника.
- Додаємо реєстрацію.
@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)
}
}
- Впроваджуємо цільовий обробник та відображення обробника для контролерів.
- Підготовлюємо метадані відображення запиту.
- Отримуємо метод обробника.
- Додаємо реєстрацію .
Методи обробника
Методи обробника з анотацією @RequestMapping
мають гнучку сигнатуру і можуть вибирати з низки
підтримуваних аргументів і значень методу контролера, що повертаються.
Аргументи методу
У наступній таблиці наведено підтримувані аргументи методу контролера.
Реактивні типи (Reactor, RxJava або інші) підтримуються в аргументах, для вирішення яких потрібне блокування введення-виведення (наприклад, читання тіла запиту). Це зазначено у колонці "Опис". Реактивні типи не очікуються для аргументів, які не вимагають блокування.
Аргумент java.util.Optional
з JDK 1.8 підтримується як аргумент методу у поєднанні з анотаціями,
що мають атрибут required
(наприклад, @RequestParam
, @RequestHeader
та інші), і еквівалентний required=false
.
Аргумент методу контролера | Опис |
---|---|
|
Забезпечує доступ до повного |
|
Забезпечує доступ до запиту або відповіді HTTP. |
|
Забезпечує доступ до сесії. Це не призводить до початку нової сесії, якщо не додано атрибути. Підтримує реактивні типи. |
|
Поточний автентифікований користувач — можливо, конкретний клас реалізації |
|
HTTP-метод запиту. |
|
Поточні регіональні налаштування запиту, що визначаються найбільш конкретним доступним |
|
Часовий пояс, пов'язаний з поточним запитом, визначений |
|
Призначений для забезпечення доступу до змінних шаблонів URI-ідентифікаторів. |
|
Призначений для забезпечення доступу до пар "ім'я-значення" в сегментах шляху URI-ідентифікаторів. |
|
Забезпечує доступ до параметрів запиту. Значення параметрів перетворюються на оголошений тип аргументів методу. Зверни увагу, що використовувати анотацію |
|
Призначений для забезпечення доступу до заголовків запитів. Значення заголовків перетворюються на оголошений тип аргументу методу. Призначений для доступу до cookie. Значення cookie перетворюються на оголошений тип аргументу методу. |
|
Забезпечує доступ до HTTP-запиту. Вміст тіла перетворюється на оголошений тип аргументу методу за
допомогою екземплярів |
|
Призначений для забезпечення доступу до заголовків та тіла запиту. Тіло перетворюється за
допомогою екземплярів |
|
Забезпечує доступ до компоненту у запиті |
|
Призначені для забезпечення доступу до моделі, яка використовується в HTML-контролерах і відображається у шаблонах як частина візуалізації подання. |
|
Призначений для забезпечення доступу до існуючого атрибуту в моделі (примірник якого створюється,
якщо він відсутній) з прив'язкою даних та валідацією. Зверни увагу, що використовувати анотацію
|
|
Забезпечують доступ до помилок валідації та прив'язки даних об'єкта команди, тобто аргумент
|
|
Призначений для позначення завершення обробки форми, що викликає очищення атрибутів сесії,
оголошених через анотацію |
|
Призначений для підготовки URL-адреси, пов'язаної з хостом, портом, схемою та контекстним шляхом поточного запиту. |
|
Призначений для забезпечення доступу до будь-якого атрибуту сесії, на відміну від атрибутів
моделі, що зберігаються в сесії в результаті оголошення анотації |
|
Призначений для забезпечення доступу до атрибутів запиту. |
Будь-який інший аргумент |
Якщо аргумент методу не відповідає жодному з перелічених вище, він за замовчуванням дозволяється
як |
Значення, що повертаються
У наступній таблиці наведено підтримувані значення методу контролера. Зверни увагу, що реактивні типи з таких бібліотек, як Reactor, RxJava або інших, зазвичай підтримуються для всіх значень, що повертаються.
Значення методу контролера, що обертається | Опис |
---|---|
|
Значення, що повертається, кодується через екземпляри |
|
Значення, що повертається, вказує, що повна відповідь, включно з HTTP-заголовками і тілом кодуватимуться
через екземпляри |
|
Призначено для повернення відповіді із заголовками і без тіла. |
|
Ім'я подання, яке має бути розпізнане за допомогою екземплярів |
|
Екземпляр |
|
Атрибути для додавання до неявної моделі, при цьому ім'я подання неявно визначається на основі шляху запиту. |
|
Атрибут, що додається до моделі, якщо при цьому маючи уявлення неявно визначається на основі шляху запиту. Зверни увагу, що анотація |
|
API для сценаріїв візуалізації моделей та уявлень. |
|
Вважається, що метод з типом, що повертається Якщо жодне зперелічених вище значень не вірне, повертається тип |
|
Пов'язаний з генерацією подій, що посилаються сервером. Функцію-обгортку
|
Будь-яке інше значення, що повертається |
Якщо значення, що повертається, не відповідає жодному з вищезазначених, воно за замовчуванням
обробляється як ім'я подання, якщо це |
Перетворення типів
Деякі анотовані аргументи методу контролера, які представляють введення запиту
на основі рядків (такі як @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-змінну сегмент шляху, де очікуються матричні змінні. У цьому прикладі показано, як це зробити:
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
fun findPet(@PathVariable petId: String, @MatrixVariable q: Int) {
// petId == 42
// q == 11
}
Враховуючи, що всі сегменти шляху можуть містити матричні змінні, іноді може знадобитися визначити, в якому змінному шляху має бути матрична змінна, як показано в наступному прикладі:
// 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
}
@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
}
Можна визначити матричну змінну як необов'язкову та вказати значення за замовчуванням, як показано в наступному прикладі:
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
// GET /pets/42
@GetMapping("/pets/{petId}")
fun findPet(@MatrixVariable(required = false, defaultValue = "1") q: Int) {
// q == 1
}
Щоб отримати всі змінні матриці, використовуй MultiValueMap
, як показано на прикладі:
// 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]
}
// 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
для прив'язування параметрів запиту до аргументу
методу в контролері. Наступний фрагмент коду демонструє використання:
@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";
}
// ...
}
- Використання анотації
@ RequestParam
.
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"
}
// ...
}
- Використання анотації
@RequestParam
.
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
:
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding, (1)
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
- Отримуємо значення заголовка
Accept-Encoding
. - Отримуємо значення заголовка
Keep-Alive
.
@GetMapping("/demo")
fun handle(
@RequestHeader("Accept-Encoding") encoding: String, (1)
@RequestHeader("Keep-Alive") keepAlive: Long) {
//...
}
- Отримуємо значення заголовка
Accept-Encoding
. - Отримуємо значення заголовка
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:
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}
- Отримання значення cookie.
@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) {
//...
}
- Отримання значення cookie.
Якщо тип параметра цільового методу не є String
, перетворення типів застосовується автоматично.
@ModelAttribute
Ти можеш використовувати анотацію @ModelAttribute
на аргументі методу доступу до атрибуту з
моделі або створення його екземпляра, якщо він відсутній. На атрибут моделі також накладаються значення
параметрів запиту та полів форми, назви яких збігаються із назвами полів. Це називається зв'язуванням даних,
і воно позбавляє необхідності парсити і перетворювати окремі параметри запиту та поля форми. У цьому
прикладі показано, як прив'язується екземпляр Pet
:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }
- Прив'язуємо екземпляр
Pet
.
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String { }
- Прив'язуємо екземпляр
Pet
.
Екземпляр Pet
у попередньому прикладі дозволяється таким чином:
З моделі, якщо вона вже додана через
Model
.З HTTP-сесії через анотацію
@SessionAttributes
.З виклику конструктора за замовчуванням.
З виклику "головного конструктора" з аргументами, що відповідають параметрам запиту або полям форми. Імена аргументів визначаються через анотацію
@ConstructorProperties
для JavaBeans або через імена параметрів, що зберігаються під час виконання, в байт-коді.
Після отримання екземпляра атрибута моделі застосовується прив'язування даних. Клас
WebExchangeDataBinder
зіставляє імена параметрів запиту та полів форми з іменами полів цільового Object
. Відповідні
поля заповнюються після перетворення типів, де це необхідно.
Прив'язка даних може призвести до помилок. За замовчуванням генерується WebExchangeBindException
,
але для перевірки таких помилок у методі контролера можна додати аргумент BindingResult
безпосередньо поряд з анотацією @ModelAttribute
, як показано в наступному прикладі :
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
- Додати
BindingResult
.
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String {
if (result.hasErrors()) {
return "petForm"
}
// ...
}
- Додати
BindingResult
.
Можна автоматично застосовувати валідацію після прив'язки даних, додавши анотацію javax.validation.Valid
або анотацію @Validated
із Spring. У цьому прикладі використовується анотація
@Valid
:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
- Використання анотації
@ Valid
для аргументу атрибута моделі.
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String {
if (result.hasErrors()) {
return "petForm"
}
// ...
}
- Використання анотації
@Valid
для аргументу атрибуту моделі.
Spring WebFlux, на відміну від Spring MVC, підтримує реактивні типи в моделі — наприклад,
Mono<Account>
або io.reactivex.Single<Account>
. Ти можеш оголосити аргумент, позначений анотацією
@ModelAttribute
, з функцією-обгорткою реактивного типу або без неї, після чого він буде
дозволений відповідним чином у фактичне значення, якщо це необхідно. Однак зверни увагу, що для
використання аргументу BindingResult
перед ним необхідно оголосити аргумент з анотацією
@ModelAttribute
без функції-обгортки реактивного типу, як було показано раніше. До того ж, можна
обробляти будь-які помилки через реактивний тип, як це показано в наступному прикладі:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public Mono<String> processSubmit(@Valid @ModelAttribute("pet") Mono<Pet> petMono) {
return petMono
.flatMap(pet -> {
// ...
})
.onErrorResume(ex -> {
// ...
});
}
@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
між
запитами. Це анотація лише на рівні типу, яка оголошує атрибути сесії, використовувані конкретним контролером.
Тут зазвичай перераховуються імена атрибутів моделі або типи атрибутів моделі, які повинні прозоро зберігатися в
сесії для наступних запитів на доступ.
Розглянемо наступний приклад:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
- Використання анотації
@SessionAttributes
.
@Controller
@SessionAttributes("pet")
class EditPetForm {
// ...
}
- Використання анотації
@SessionAttributes
.
При першому запиті, якщо модель додається до атрибуту моделі з ім'ям pet
, він автоматично просувається
в WebSession
і зберігається в ній. Він залишається там, поки інший метод контролера не використовує
аргумент методу SessionStatus
для очищення сховища, як показано в наступному прикладі:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors()) {
// ...
}
status.setComplete();
// ...
}
}
}
- Використання анотації
@SessionAttributes
. - Використання змінної
SessionStatus
.
@Controller
@SessionAttributes("pet")
class EditPetForm {
// ...
@PostMapping("/pets/{id}")
fun handle(pet: Pet, errors: BindingResult, status: SessionStatus): String {
if (errors.hasErrors()) {
// ...
}
status.setComplete()
// ...
}
}
- Використання анотації
@SessionAttributes
. - Використання змінної
SessionStatus
.
@SessionAttribute
Якщо потрібний доступ до заздалегідь існуючих атрибутів сесії, які управляються глобально (тобто поза контролером
— наприклад, фільтром) і можуть бути присутніми або відсутніми, то можна використовувати анотацію @SessionAttribute
для параметра методу, як показано в наступному прикладі:
@GetMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
- Використання анотації
@SessionAttribute
.
@GetMapping("/")
fun handle(@SessionAttribute user: User): String {
// ...
}
- Використання анотації
@SessionAttribute
.
Для випадків використання, які вимагають додавання або видалення атрибутів сесії, зверни увагу на можливість
впровадження WebSession
у метод контролера.
Для тимчасового зберігання атрибутів моделі в сесії як частини робочого процесу контролера, розглянь можливість
використання SessionAttributes
.
@RequestAttribute
Аналогічно анотації @SessionAttribute
, можна використовувати анотацію @RequestAttribute
для здійснення доступу до вже існуючих атрибутів запиту, створеного раніше (наприклад, WebFilter
),
як показано в наступному прикладі:
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
- Використання анотації
@RequestAttribute
.
@GetMapping("/")
fun handle(@RequestAttribute client: Client): String {
// ...
}
- Використання анотації
@RequestAttribute
.
Багатокомпонентний вміст
ServerWebExchange
забезпечує доступ до багатокомпонентного вмісту. Найкращий спосіб обробки форми
завантаження файлу (наприклад, з браузера) у контролері — це прив'язка даних до об'єкта команди, як показано в
наступному прикладі:
class MyForm {
private String name;
private MultipartFile file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
// ...
}
}
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
, як показано в
наступному прикладі:
@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata,
@RequestPart("file-data") FilePart file) {
// ...
}
- Використання анотації
@RequestPart
для отримання метаданих. - Використання анотації
@RequestPart
для отримання файлу.
@PostMapping("/")
fun handle(@RequestPart("meta-data") Part metadata,
@RequestPart("file-data") FilePart file): String {
// ...
}
- Використання анотації
@RequestPart
для отримання метаданих. - Використання анотації
@RequestPart
для отримання файлу.
Щоб десеріалізувати сирий вміст компонента (наприклад, в JSON — аналогічно інструкції @RequestBody
),
можна оголосити конкретний цільовий Object
замість Part
, як показано в наступному
прикладі:
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) {
// ...
}
- Використання анотації
@RequestPart
для отримання метаданих.
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData): String {
// ...
}
- Використання анотації
@RequestPart
для отримання метаданих.
Можно використати анотацію @RequestPart
у поєднанні з анотацією javax.validation.Valid
або анотацією @Validated
зі Spring, що призведе до застосування стандартної Bean Validation.
Помилки валідації призводять до виникнення WebExchangeBindException
, внаслідок чого видається
відповідь 400 (BAD_REQUEST). Виняток містить BindingResult
з деталями помилки і також може бути
оброблено в методі контролера шляхом оголошення аргументу з асинхронною функцією-оберткою та подальшого
використання операторів, пов'язаних з помилкою:
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
// використовуємо один з операторів onError*...
}
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
// ...
}
Щоб отримати доступ до всіх багатокомпонентних даних у вигляді MultiValueMap
, ви можеш
використовувати анотацію @RequestBody
, як це показано в наступному прикладі:
@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) {
// ...
}
- Використання анотації
@RequestBody
.
@PostMapping("/")
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String {
// ...
}
- Використання анотації
@RequestBody
.
Для послідовного потокового доступу до багатокомпонентних даних можна використовувати анотацію
@RequestBody
з Flux<Part>
(або Flow<Part>
у Kotlin), як показано в наступному
прикладі:
@PostMapping("/")
public String handle(@RequestBody Flux<Part> parts) {
// ...
}
- Використання анотації
@RequestBody
.
@PostMapping("/")
fun handle(@RequestBody parts: Flow<Part>): String {
// ...
}
- Використання анотації
@RequestBody
.
@RequestBody
Анотацію @RequestBody
можна використовувати, щоб прочитати тіло запиту і десеріалізувати
його в об'єкт
Object HttpMessageReader. У цьому прикладі використовується аргумент,
анотований @RequestBody
:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
@PostMapping("/accounts")
fun handle(@RequestBody account: Account) {
// ...
}
На відміну від Spring MVC, у WebFlux аргумент методу, позначеного анотацією @RequestBody
,
підтримує реактивні типи і повністю неблокуюче читання та (клієнт-серверну) потокову передачу.
@PostMapping("/accounts")
public void handle(@RequestBody Mono<Account> account) {
// ...
}
@PostMapping("/accounts")
fun handle(@RequestBody accounts: Flow<Account>) {
// ...
}
Ти можеш використовувати опцію кодеків повідомлень, що передаються за протоколом HTTP, в конфігурації WebFlux для конфігурування або кастомного налаштування засобів читання повідомлень.
Анотацію @RequestBody
можна застосовувати в поєднанні з анотацією javax.validation.Valid
або анотацією
@Validated
із Spring, що призводить до застосування стандартної Bean Validation. Помилки валідації
викликають WebExchangeBindException
, що призводить до відповіді 400 (BAD_REQUEST). Виняток містить
BindingResult
з деталями помилки і також може бути оброблено в методі контролера шляхом оголошення
аргументу з асинхронною функцією-обгорткою та подальшого використання операторів, пов'язаних з помилкою:
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Mono<Account> account) {
// використовуємо один з операторів onError*...
}
@PostMapping("/accounts")
fun handle(@Valid @RequestBody account: Mono<Account>) {
// ...
}
HttpEntity
HttpEntity
більш-менш ідентичний анотації @RequestBody
в плані використання, але
ґрунтується на об'єкті-контейнер, який відкриває заголовки та тіло запиту. У цьому прикладі
використовується HttpEntity
:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
@PostMapping("/accounts")
fun handle(entity: HttpEntity<Account>) {
// ...
}
@ResponseBody
Анотацію @ResponseBody
можна використовувати нам методом, щоб повернення серіалізувалося в
тіло відповіді через HttpMessageWriter. У цьому прикладі показано, як це зробити:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
@GetMapping("/accounts/{id}")
@ResponseBody
fun handle(): Account {
// ...
}
@ResponseBody
також підтримується лише на рівні класу, у разі вона успадковується всіма
методами контролера. У цьому полягає дія анотації @RestController
, яка є нічим іншим, як
мета-анотацією, позначеною анотаціями @Controller
та @ResponseBody.
@ResponseBody
підтримує реактивні типи, що означає, що ти можеш повертати типи Reactor або
RxJava і отримувати відповідні асинхронні значення, які вони створюють.
Ти можеш комбінувати методи, позначені анотацією @ResponseBody
, з представленнями
серіалізації формату JSON.
Можна використовувати опцію кодеків повідомлень, що передаються за протоколом HTTP, у конфігурації WebFlux для конфігурування або кастомного налаштування пристроїв читання повідомлень.
ResponseEntity
Атрибут ResponseEntity
подібний до анотації
@ResponseBody
,
але зі статусом і заголовками. Наприклад:
@GetMapping("/something")
public ResponseEntity<String> handle() {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).body(body);
}
@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< code>Mono<ResponseEntity<Flux<T>>>
є ще однією можливою, хоча і менш поширеною альтернативою. Спочатку вони асинхронно надають статус відповіді та заголовки, а потім, у другу чергу, тіло відповіді також асинхронно.
Jackson JSON
Spring передбачає підтримку бібліотеки Jackson JSON.
Подання JSON
Spring WebFlux передбачає вбудовані засоби підтримки подання
серіалізації з бібліотеки Jackson, яка дозволяє візуалізувати виключно підмножину всіх полів Object
.
Щоб використовувати його з методами контролера, анотованими @ResponseBody
, або класом
ResponseEntity
, можна використовувати анотацію @JsonView
з Jackson для
активації класу представлення серіалізації, як показано в наступному прикладі:
@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;
}
}
@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
:
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}}
@ModelAttribute
fun populateModel(@RequestParam number: String, model: Model) {
model.addAttribute(accountRepository.findAccount(number))
// add more ...
}
У наступному прикладі додано тільки один атрибут:
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}
@ModelAttribute
fun addAccount(@RequestParam number: String): Account {
return accountRepository.findAccount(number);
}
угод
. Завжди можна присвоїти явне ім'я за допомогою
перевантаженого методу addAttribute
або через атрибут name
в анотації @ModelAttribute
(для значення, що повертається).
Spring WebFlux, на відміну від Spring MVC, явно підтримує реактивні типи в моделі (наприклад,
Mono<Account>
або io.reactivex.Single<Account>
). Такі асинхронні атрибути моделі можна прозоро
дозволити (і оновити модель) у їх фактичні значення під час виклику @RequestMapping
, якщо
аргумент з анотацією @ModelAttribute
оголошено без функції-обгортки, як показано у наступному
прикладі:
@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) {
// ...
}
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
також може допомогти кастомно налаштувати ім'я атрибута
моделі, як показано в наведеному нижче прикладі:
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
// ...
return account;
}
@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
:
@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));
}
// ...
}
- Використання анотації
@ InitBinder
.
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
,
специфічні для контролера, як показано в наступному прикладі:
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
- Додати форматер користувача (в даному випадку
DateFormatter
).
@Controller
class FormController {
@InitBinder
fun initBinder(binder: WebDataBinder) {
binder.addCustomFormatter(DateFormatter("yyyy-MM-dd"))
}
// ...
}
- Додати форматер користувача (в даному випадку
DateFormatter
).
Структура моделі
У контексті вебдодатків прив'язування даних передбачає прив'язку параметрів HTTP-запиту (тобто даних форми або параметрів запиту) до властивостей об'єкта моделі та його вкладених об'єктів.
Для прив'язки
даних відкриті лише public
властивості, що відповідають угоди про ім'я
JavaBeans – наприклад, методи public String getFirstName()
та public void
setFirstName(String)
для властивості firstName
.
За замовчуванням 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
, для обробки винятків із методів контролера. Наступний приклад містить
такий метод обробника:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
- Оголошення анотації
@ExceptionHandler
.
class SimpleController {
// ...
@ExceptionHandler
fun handle(ex: IOException): ResponseEntity<String> {
// ...
}
}
- Оголошення анотації
@ExceptionHandler
.
Виняток може збігатися з поширюваним високорівневим винятком (наприклад, прямо генерований
IOException
) або з вкладеною причиною всередині виключення-обгортки (наприклад,
IOException
, обернене всередині IllegalStateException
).
Для типів
винятків, що збігаються, найкраще оголошувати цільовий виняток як аргумент методу, як показано у попередньому
прикладі. До того ж,
оголошення анотації може звужувати типи винятків, які повинні збігатися. Зазвичай ми рекомендуємо бути якомога
конкретнішими в сигнатурі аргументу і оголошувати головні відображення кореневих винятків на анотацію @ControllerAdvice
у відповідному порядку.
@ExceptionHandler
у WebFlux підтримує ті ж
аргументи методу і значення, що повертаються, що і метод з анотацією @RequestMapping
, за винятком
аргументів методу, пов'язаних з тілом запиту і анотацією @ModelAttribute
.
Підтримка методів, анотованих @ExceptionHandler
, Spring WebFlux забезпечується за допомогою
HandlerAdapter
для методів з анотацією @RequestMapping
.
Робота з винятками в REST API
Спільною вимогою
для служб на основі REST є включення інформації про помилку в тіло відповіді. Spring Framework не здійснює це
автоматично, оскільки вказівка інформації про помилку у тілі відповіді залежить від конкретної програми. Однак
анотація @RestController
може використовувати методи, позначені анотацією
@ExceptionHandler
, з значенням ResponseEntity
, що повертається, для встановлення статусу і
тіла відповіді. Такі методи також можуть бути оголошені в класах з анотацією @ControllerAdvice
, щоб
застосовувати їх на глобальному рівні.
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
застосовуються до кожного запиту (тобто
всіх
контролерів), але можна звузити коло контролерів, використовуючи атрибути анотації, як показано в наступному
прикладі:
// Вибираємо метою всі контролери, анотовані за допомогою @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Вибираємо метою всі контролери в певних пакетах
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Вибираємо метою всі контролери, які призначаються певним класам
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
// Вибираємо метою всі контролери, анотовані за допомогою @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
.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ