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

Аргументи методу

У наступній таблиці описані підтримувані аргументи методу контролера. Реактивні типи не підтримуються для жодних аргументів.

Аргумент java.util.Optional з JDK 8 підтримується як аргумент методу в поєднанні з анотаціями, що мають атрибут required (наприклад, @RequestParam, @RequestHeader та інші), і еквівалентний required=false.

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

WebRequest, NativeWebRequest

Забезпечує типізований доступ до параметрів запиту та атрибутів запиту та сесії без прямого використання Servlet API.

javax.servlet. ServletRequest, javax.servlet.ServletResponse

Призначений для вибору будь-якого конкретного типу запиту або відповіді — наприклад, ServletRequest, HttpServletRequest або MultipartRequest, MultipartHttpServletRequest зі Spring.

javax.servlet.http.HttpSession

Забезпечує наявність сесії. Як наслідок, такий аргумент ніколи не null. Зверни увагу, що доступ до сесії не є безпечним. Розглянь можливість встановлення прапора synchronizeOnSession екземпляра RequestMappingHandlerAdapter у true, якщо одночасний доступ до сесії дозволено декількома запитами.

javax.servlet.http.PushBuilder

API проштовхувального засобу складання Servlet 4.0 для програмного проштовхування (передачі ) ресурсів HTTP/2. Зверни увагу, що, згідно специфікації Servlet, впроваджуваний екземпляр PushBuilder може бути порожнім, якщо клієнт не підтримує цю функцію HTTP/2.

java.security.Principal

Поточний автентифікований користувач — можливо, конкретний клас реалізації Principal, якщо він відомий.

Зверни увагу, що цей аргумент не розпізнається прискорено, якщо має анотацію, призначену для того, щоб дозволити кастомному розпізнавачу розпізнати його, перш ніж повернутися до розпізнавання за замовчуванням через HttpServletRequest#getUserPrincipal. Наприклад, Authentication зі Spring Security реалізує Principal і буде впроваджена як така через HttpServletRequest#getUserPrincipal, якщо тільки не буде також анотована @AuthenticationPrincipal , інакше в цьому випадку вона буде дозволена кастомним розпізнавачем Spring Security через Authentication#getPrincipal.

HttpMethod

HTTP-метод запиту.

java.util.Locale

Поточні регіональні налаштування запиту, що визначаються найбільш конкретним доступним LocaleResolver (по суті, налаштованим LocaleResolver або LocaleContextResolver).

java.util.TimeZone + java.time.ZoneId

Часовий пояс, пов'язаний з поточним запитом, визначений LocaleContextResolver.

java.io.InputStream, java.io.Reader

Призначений для забезпечення доступу до сирого тіла запиту в тому вигляді, в якому воно представлене в Servlet API.

java.io.OutputStream, java.io.Writer

Призначений для забезпечення доступу до сирого тіла відповіді, відкритого Servlet API.

@PathVariable

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

@MatrixVariable

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

@RequestParam

Призначений для доступу до параметрів запиту сервлета, включно з багатокомпонентними файлами. Значення параметрів перетворюються на оголошений тип аргументів методу.

Зверни увагу, що використання анотації @RequestParam не є обов'язковим для простих значень параметрів. Див. "Будь-який інший аргумент" наприкінці цієї таблиці.

@RequestHeader

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

@CookieValue

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

@RequestBody

Для доступу до HTTP-запиту. Вміст тіла перетворюється на оголошений тип аргументу методу за допомогою реалізацій HttpMessageConverter.

HttpEntity<B>

Призначений для забезпечення доступу до заголовків та тіла запиту. Тіло перетворюється за допомогою HttpMessageConverter.

@RequestPart

Призначений для забезпечення доступу до компоненту в запиті multipart/form-data шляхом перетворення тіла цього компонента за допомогою HttpMessageConverter.

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

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

RedirectAttributes

Встановлює атрибути, які будуть використовуватися у разі перенаправлення (тобто будуть додані до рядку запиту), flash атрибути, які тимчасово зберігатимуться до надходження запиту після перенаправлення.

@ModelAttribute

Для доступу до існуючого атрибуту в моделі (екземпляр якого створюється, якщо він відсутній) з прив'язкою даних та валідацією.

Зверни увагу, що використовувати анотацію @ ModelAttribute необов'язково (наприклад, для встановлення його атрибутів). Див. "Будь-який інший аргумент" наприкінці цієї таблиці.

Errors, BindingResult

Призначений для забезпечення доступу до помилок валідації та прив'язки даних для об'єкта команди (тобто аргументу з анотацією @ModelAttribute) або до помилок перевірки аргументів з анотаціями @RequestBody або @RequestPart. Оголошувати аргумент Errors або BindingResult потрібно відразу після аргументу методу, що валідується.

SessionStatus + class-level @SessionAttributes

Призначений для позначення завершення обробки форми, що викликає очищення атрибутів сесії, оголошених через анотацію @SessionAttributes на рівні класу.

UriComponentsBuilder

Призначений для підготовки URL-адреси, пов'язаної з хостом, портом, схемою, контекстним шляхом поточного запиту та літеральним компонентом відображення сервлету.

@SessionAttribute

Призначений для забезпечення доступу до будь-якого атрибуту сесії, на відміну від атрибутів моделі, що зберігаються в сесії в результаті оголошення анотації @SessionAttributes на рівні класу.

@RequestAttribute

Призначений для забезпечення доступу до атрибутів запиту.

Будь-який інший аргумент

Якщо аргумент метод не відповідає жодному з попередніх значень у цій таблиці і є простим типом (як визначено BeanUtils#isSimpleProperty), він дозволяється як @RequestParam. В іншому випадку він дозволяється як @ModelAttribute.

Значення, що повертаються

У наступному таблиці описані підтримувані значення методу контролера. Реактивні типи підтримуються для всіх значень, що повертаються.

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

@ResponseBody

Значення, що повертається, перетворюється через реалізацію HttpMessageConverter і записується у відповідь.

HttpEntity<B>, ResponseEntity<B>

Значення, що повертається, яке зазначає повну відповідь (включно з HTTP-заголовками і тілом), перетворюється через реалізацію HttpMessageConverter і записується у відповідь.

HttpHeaders

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

String

Ім'я подання, яке має бути розпізнане за допомогою реалізацій ViewResolver і використовуватися разом з неявною моделлю, визначається через об'єкти команд та методи з анотацією @ModelAttribute. Метод обробника також може програмно спеціально доопрацювати модель, оголосивши аргумент Model.

View

Екземпляр View, призначений для візуалізації разом з неявною моделлю — визначається через об'єкти команд та методи з анотацією @ModelAttribute. Метод обробника також може програмно спеціально доопрацювати модель, оголосивши аргумент Model.

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

Атрибути, що додаються до неявної моделі, з ім'ям подання, що неявно визначається через RequestToViewNameTranslator .

@ModelAttribute

Атрибут, доданий до моделі, ім'я подання якого неявно визначається через RequestToViewNameTranslator.

Зверни увагу, що анотація @ModelAttribute є необов'язковою. Див. розділ "Будь-яке інше значення, що повертається" в кінці цієї таблиці.

ModelAndView object

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

void

Вважається, що метод з типом void, що повертається (або значенням, що повертається null) повністю обробив відповідь, якщо він також має ServletResponse, аргумент OutputStream або анотацію @ResponseStatus. Те саме вірно, якщо контролер виконав позитивну перевірку ETag або тимчасової мітки lastModified.

Якщо жодне з вищеперелічених значень не вірне, повертається тип void може також вказувати на "відсутність тіла відповіді" для REST-контролерів або вибір імені подання за промовчанням для HTML-контролерів.

DeferredResult<V>

Здійснює будь-яке з попередніх значень, що повертаються, асинхронно з будь-якого потоку — наприклад, в результаті будь-якої події або зворотного виклику.

Callable<V>

Перетворює будь-яке з перерахованих вище значень, що повертаютья асинхронно в потоці, керованому Spring MVC.

ListenableFuture<V>, java.util.concurrent.CompletionStage<V>, java.util.concurrent.CompletableFuture<V>

Найбільш зручна альтернатива DeferredResult (наприклад, якщо основний сервіс повертає один з них).

ResponseBodyEmitter, SseEmitter

Асинхронно породжує потік об'єктів для запису у відповідь за допомогою реалізацій HttpMessageConverter. Також підтримується як тіло для ResponseEntity.

StreamingResponseBody

Асинхронний запис в OutputStream відповіді. Також підтримується як тіло для ResponseEntity.

Реактивні типи — Reactor, RxJava, або інші через ReactiveAdapterRegistry

Альтернатива DeferredResult з багатозначними потоками (наприклад, Flux, Observable), зібраними в List.

У потокових сценаріях (наприклад, text/event-stream, application/json+stream) замість них використовуються SseEmitter і ResponseBodyEmitter, де блокуюче введення-виведення ServletOutputStream виконується в потоці, керованому Spring MVC, а зворотна реакція відбувається після завершення кожного запису.

Будь-яке інше значення, що повертається

Будь-яке значення, що повертається, яке не відповідає жодному з попередніх значень у цій таблиці і є String або void, розглядається як ім'я подання (застосовується вибір імені подання за замовчуванням через RequestToViewNameTranslator), якщо воно не є простим типом, як визначено BeanUtils#isSimpleProperty. Значення, що є простими типами, залишаються невирішеними.

Перетворення типу

Деякі анотовані аргументи методу контролера, які представляють введення запиту на основі String (такі як @RequestParam, @RequestHeader, @PathVariable, @MatrixVariable і @CookieValue), можуть вимагати перетворення типу, якщо аргумент оголошений не як String.

У таких випадках перетворення типу застосовується автоматично на основі конфігурованих перетворювачів. За замовчуванням підтримуються прості типи (int, long, Date та інші). Ти можеш налаштувати перетворення типів через WebDataBinder або через реєстрацію Formatters у FormattingConversionService.

Практичним питанням при перетворенні типів є обробка порожнього вихідного значення рядка. Таке значення розглядається як відсутнє, якщо воно стає null у результаті перетворення типу. Це може бути характерним для Long, UUID та інших цільових типів. Якщо тобі потрібно допустити впровадження null, або використовуй прапор required в анотації аргументу, або оголоси аргумент як @Nullable.

Починаючи з версії 5.3, не-null аргументи будуть виконуватися навіть після перетворення типів. Якщо передбачається, що метод обробника буде приймати і порожнє значення, оголоси аргумент як @Nullable, або познач його як required=false у відповідній анотації @RequestParam тощо. Це найкращий спосіб і рекомендоване рішення для регресій, що виникають при оновленні до версії 5.3. @PathVariable є обов'язковим. Null-значення після перетворення буде оброблено як порожнє вихідне значення, тому буде згенеровано відповідні варіанти винятку Missing…Exception.

Матричні змінні

У RFC 3986 описані пари "ім'я-значення" в сегментах шляху. У Spring MVC ми називаємо їх "матричними змінними", ґрунтуючись на "старому пості" Тіма Бернерса-Лі, але їх також можна назвати параметрами шляху URI-ідентифікатора. Матричні змінні можуть з'являтися в будь-якому сегменті шляху, водночас кожна змінна відділяється точкою з комою, а кілька значень – комою (наприклад, /cars;color=red,green;year=2012). Множинні значення також можна вказати через імена змінних, що повторюються (наприклад, color=red;color=green;color=blue).

Якщо очікується, що URL-адреса міститиме матричні змінні, то відображення запиту для методу контролера повинно використовувати змінну 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

// GET /owners/42;q=11/pets/21;q=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
}

Матрична змінна може бути визначена як необов'язкова і мати значення за замовчуванням, як це показано в наступному прикладі:

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]
}

Зверни увагу, що необхідно активувати використання матричних змінних. У конфігурації MVC Java необхідно встановити UrlPathHelper при removeSemicolonContent=false через Path Matching. У просторі імен MVC на XML можна встановити <mvc:annotation-driven enable-matrix-variables="true"/>.

@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 для прив'язки petId.
Kotlin
import org.springframework.ui.set
@Controller
@RequestMapping("/pets" )
class EditPetForm {
    // ...
    @GetMapping
    fun setupForm(@RequestParam("petId") petId: Int, model: Model): String { 
        val pet = this.clinic.loadPet(petId);
        model["pet"] = pet
        return "petForm"
    }
    // ...
  1. Використання анотації @RequestParam для прив'язки petId.

За замовчуванням параметри методу, що використовують цю анотацію, є обов'язковими, але ти можеш вказати, що параметр методу є необов'язковим, встановивши прапор required анотації @RequestParam у false або якщо оголосиш аргумент за допомогою функції-обгортки java.util. Optional.

Перетворення типу застосовується автоматично, якщо тип параметра цільового методу не String.

Оголошення типу аргументу як масиву або списку дозволяє кілька значень параметрів для одного і того ж імені параметра.

Якщо анотація @RequestParam оголошена як Map<String, String> або MultiValueMap<String, String> без зазначення імені параметра в анотації, то Map заповнюється значеннями параметрів запиту для кожного заданого імені параметра.

Зверни увагу, що використання анотації @RequestParam є необов'язковим (наприклад, для встановлення атрибутів). За замовчуванням будь-який аргумент, який є простим типом значення (як визначено BeanUtils#isSimpleProperty) і не дозволений жодним іншим розпізнавачем аргументів, обробляється так, якби він був анотований @RequestParam.

Розглянемо наступний запит із заголовками:

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, 
        @RequestHeader("Keep-Alive") long keepAlive) { 
    //...
}
  1. Отримуємо значення заголовка Accept-Encoding.
  2. Отримуємо значення заголовка Keep-Alive.
Kotlin
@GetMapping("/demo")
fun handle(
        @RequestHeader("Accept-Encoding") encoding: String, 
        @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 JSESSIONID.
Kotlin
@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) { 
    //...
}
  1. Отримання значення cookie JSESSIONID.

Якщо тип параметра цільового методу не String, перетворення типів застосовується автоматично.

@ModelAttribute

Анотацію @ModelAttribute можна використовувати для аргументу методу, щоб отримати доступ до атрибуту з моделі або створити його екземпляр, якщо він відсутній. На атрибут моделі також накладаються значення параметрів запиту сервлета за протоколом HTTP, імена яких збігаються з іменами полів. Це називається прив'язуванням даних, і воно позбавляє необхідності парсити і перетворювати окремі параметри запиту та поля форм. У цьому прикладі показано, як це зробити:

Java

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) {
    // method logic...
}
Kotlin

@PostMapping( "/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String {
    // method logic...
}

Наведений вище екземпляр Pet отриманий одним із наступних способів:

  • Витягується з моделі, куди його могли додати методом з анотацією @ModelAttribute .

  • Витягується з HTTP-сесії, якщо атрибут моделі був вказаний в анотації @SessionAttributes на рівні класу.

  • Отриманий через якийсь Converter, де ім'я атрибуту моделі збігається з ім'ям значення запиту, наприклад змінної шляху або параметром запиту (див. наступний приклад).

  • Створюється за допомогою конструктора за замовчуванням.

  • Створюється через "первинний конструктор" з аргументами, що відповідають параметрам запиту сервлета. Імена аргументів визначаються через анотацію @ConstructorProperties класів JavaBeans або через імена параметрів, що зберігаються під час виконання, в байт-коді.

Альтернативою використанню методу з анотацією @ModelAttribute для його надання або звернення до фреймворку для створення атрибуту моделі є використання Converter<String, T> для надання екземпляра. Це застосовується, якщо ім'я атрибута моделі збігається з ім'ям значення запиту, такого як змінна шляху або параметр запиту, і існує Converter з String у типу атрибута моделі. У наступному прикладі ім'я атрибуту моделі — account, що відповідає змінній шляху URI-ідентифікатора account, а також існує зареєстрований Converter<String, Account>, який може завантажити Account зі сховища даних:

Java

@PutMapping("/accounts/{account}")
public String save( @ModelAttribute("account") Account account) {
    // ...
}
Kotlin

@PutMapping ("/accounts/{account}")
fun save(@ModelAttribute("account") account: Account): String {
    // ...
}

Після отримання екземпляра атрибуту моделі застосовується прив'язка даних. Клас WebDataBinder зіставляє імена параметрів запиту сервлета (параметри запиту та поля форми) з іменами полів цільового об'єкта. Узгоджені поля заповнюються після застосування перетворення типу, де це необхідно.

Прив'язка даних може призвести до помилок. За замовчуванням виникає виняток BindException. Однак для перевірки таких помилок у методі контролера можна додати аргумент 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 поряд з анотацією @ModelAttribute.
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/ edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { 
    if (result.hasErrors ()) {
        return "petForm"
    }
    // ...
}
  1. Додавання BindingResult поруч із анотацією @ModelAttribute.

У деяких випадках може знадобитися доступ до атрибуту моделі без прив'язки до даних. Для таких випадків можна впровадити Model у контролер і звертатися до неї безпосередньо або, як варіант, встановити @ModelAttribute(binding=false), як показано в наступному прикладі:

Java
@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(@Valid AccountForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) { 
    // ...
}
  1. Налаштування @ModelAttribute(binding=false).
Kotlin

@ModelAttribute
fun setUpForm(): AccountForm {
    return AccountForm()
}
@ModelAttribute
fun findAccount(@PathVariable accountId : String): Account {
    return accountRepository.findOne(accountId)
}
@PostMapping("update")
fun update(@Valid form: AccountForm, result: BindingResult,
           @ModelAttribute(binding = false) account: Account): String { 
    // ...
}
  1. Налаштування @ModelAttribute(binding=false).

Ти можеш автоматично застосовувати валідацію після прив'язки даних, додавши анотацію javax.validation.Valid або анотацію @Validated зі Spring. У цьому прикладі показано, як це зробити:

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { 
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}
  1. Валідуємо екземпляр Pet.
Kotlin
                
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute( "pet") pet: Pet, result: BindingResult): String { 
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}

Зверни увагу, що використання анотації @ModelAttribute є необов'язковим (наприклад, для встановлення атрибутів). За замовчуванням будь-який аргумент, який не є простим типом значення (як визначено BeanUtils#isSimpleProperty) і не дозволяється жодним іншим розпізнавальником аргументів, обробляється так, якби він був анотований @ModelAttribute. Це анотація лише на рівні типу, яка оголошує атрибути сесії, використовувані конкретним контролером. Тут зазвичай перераховуються імена атрибутів моделі або типи атрибутів моделі, які повинні прозорим чином зберігатися в сесії для наступних запитів на доступ.

Java
@Controller
@SessionAttributes("pet") 
public class EditPetForm {
    // ...
}
  1. Використання анотації @SessionAttributes.
Kotlin
@Controller
@SessionAttributes("pet") 
class EditPetForm {
    // ...
}
  1. Використання анотації @SessionAttributes.

При першому запиті, коли атрибут моделі з ім'ям pet додається до моделі, він автоматично просувається та зберігається в HTTP-сесії сервлета. Він залишається там, поки інший метод контролера не використовує аргумент методу 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. Зберігання значення Pet у сесії сервлету.
  2. Очищення значення Pet із сесії сервлета.
Kotlin
@Controller
@SessionAttributes("pet") 
class EditPetForm {
    // ...
    @PostMapping("/pets/{id}")
    fun handle(pet: Pet, errors: BindingResult, status: SessionStatus): String {
        if (errors.hasErrors()) {
            // ...
        }
        status.setComplete() 
        // ...
    }
}
  1. Зберігання значення Pet у сесії сервлету.
  2. Очищення значення Pet із сесії сервлета.

@SessionAttribute

Якщо потрібен доступ до заздалегідь існуючих атрибутів сесії, які управляються глобально (тобто поза контролером: наприклад, фільтром) і можуть бути присутніми або відсутніми, можна використовувати анотацію @SessionAttribute для параметра методу, як показано в наступному прикладі:

Java
@RequestMapping("/")
public String handle(@SessionAttribute User user) { 
    // ...
}
  1. Використання анотації @SessionAttribute.
Kotlin
@RequestMapping("/")
fun handle(@SessionAttribute user: User): String { 
    // ..
}

Для випадків використання, що вимагають додавання або видалення атрибутів сесії, розглянь можливість впровадження org.springframework.web.context.request.WebRequest або javax.servlet.http.HttpSession метод контролера.

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

@RequestAttribute

Подобно до анотації @SessionAttribute, можна використовувати анотації @RequestAttribute для забезпечення доступу до вже існуючих атрибутів запиту, створених раніше (наприклад, Filter або HandlerInterceptor сервлетів):

Java
@GetMapping("/")
public String handle(@RequestAttribute Client client) { 
    // ...
}
  1. Використання анотації @RequestAttribute.
Kotlin
@GetMapping("/")
fun handle(@RequestAttribute client: Client): String { 
    // ...
}
  1. Використання анотації @ RequestAttribute.

Атрибути перенаправлення

За замовчуванням всі атрибути моделі вважаються представленими у формі шаблону URI-ідентифікаторів в URL-адресі перенаправлення. З атрибутів, що залишилися, ті, які є примітивними типами або колекціями чи масивами примітивних типів, автоматично додаються як параметри запиту.

Додавання атрибутів примітивних типів як параметри запиту може бути необхідним результатом, якщо екземпляр моделі був підготовлений спеціально під перенаправлення. Однак у анотованих контролерах модель може містити додаткові атрибути, додані для цілей візуалізації (наприклад, значення полів, що випадають). Щоб уникнути появи таких атрибутів в URL-адресі, метод, позначений анотацією @RequestMapping, може оголосити аргумент типу RedirectAttributes і використовувати його для зазначення точних атрибутів, які мають бути доступні RedirectView. Якщо метод виконує перенаправлення, використовується вміст RedirectAttributes. В іншому випадку використовується вміст моделі.

RequestMappingHandlerAdapter надає прапор ignoreDefaultModelOnRedirect, який можна використовувати, щоб вказати, що вміст Model за замовчуванням в жодному разі не слід використовувати при перенаправленні методом контролера. Натомість метод контролера повинен оголошувати атрибут типу RedirectAttributes або, якщо він цього не робить, то ніякі атрибути не слід передавати RedirectView. І в просторі імен MVC, і в конфігурації MVC на Java цей прапор встановлено в false, щоб зберегти зворотну сумісність. Однак для нових програм ми рекомендуємо встановити значення true.

Зверни увагу, що змінні шаблону URI-ідентифікаторів з цього запиту автоматично стають доступними при розширенні URL-адреси перенаправлення, тому явно додавати їх через Model або RedirectAttributes не потрібно. У цьому прикладі показано, як визначити перенаправлення:

Java
@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}
Kotlin
@PostMapping("/files/{path}")
fun upload(... ): String {
    // ...
    return "redirect:files/{path}"
}

Іншим способом передачі даних до мети перенаправлення є використання flash атрибутів. На відміну від інших атрибутів перенаправлення, тимчасово flash атрибути зберігаються в HTTP-сесії (і, отже, не відображаються в URL-адресі).

Flash атрибути

Flash атрибути забезпечують можливість зберігати атрибути одного запиту, призначені для використання в іншому запиті. Найчастіше це необхідно при перенаправленні — наприклад, шаблон Post-Redirect-Get. Flash атрибути тимчасово зберігаються перед перенаправленням (зазвичай у сесії), щоб вони були доступні для запиту після перенаправлення, після чого відразу ж видаляються. Spring MVC має дві основні абстракції для підтримки flash атрибутів. FlashMap використовується для зберігання flash атрибутів, а FlashMapManager — для зберігання, отримання та управління екземплярами FlashMap.

Підтримка flash атрибутів завжди "ввімкнена" і не вимагає явної активації. Однак, якщо вона не використовується, це ніколи не призводить до створення HTTP-сесії. При кожному запиті існує "вхідний" FlashMap з атрибутами, що передалися з попереднього запиту (якщо є), і "вихідний" FlashMap з атрибутами, які необхідно зберегти для наступного запиту. Обидва екземпляри FlashMap доступні з будь-якої точки Spring MVC через статичні методи в RequestContextUtils.

Анотованим контролерам зазвичай не потрібно безпосередньо працювати з FlashMap. Натомість метод, анотований @RequestMapping, може приймати аргумент типу RedirectAttributes та використовувати його для додавання flash атрибутів для сценарію перенаправлення. Flash атрибути, додані через RedirectAttributes, автоматично поширюються на "вихідний" FlashMap. Аналогічно, після перенаправлення атрибути з "вхідного" FlashMap автоматично додаються до Model контролера, який обслуговує цільову URL-адресу.

Зіставлення запитів із flash атрибутами

Концепція flash атрибутів існує в багатьох інших вебфреймворках і, як виявилося, іноді схильна до проблем з паралелізмом. Це тому, що flash атрибути за визначенням повинні зберігатися до наступного запиту. Однак сам "наступний" запит може бути не від передбачуваного одержувача, а від іншого асинхронного запиту (наприклад, поллінга або запиту на виділення ресурсів), і в цьому випадку flash атрибути будуть видалятися надто рано.

Щоб зменшити ймовірність виникнення подібних проблем, RedirectView автоматично "позначає" екземпляри FlashMap за допомогою вказання шляху та параметрів запиту цільової URL-адреси перенаправлення. У свою чергу, FlashMapManager за замовчуванням зіставляє цю інформацію з вхідними запитами, коли шукає "вхідний" FlashMap.

Це не до кінця усуває ймовірність виникнення проблеми з паралелізмом, але значно скорочує її при використанні інформації, яка вже доступна в URL-адресі перенаправлення. Тому рекомендуємо використовувати flash атрибути в основному для сценаріїв перенаправлення.

Багатокомпонентність

Після активації MultipartResolver вміст POST-запитів з multipart/form-data парситься і стає доступним як звичайні параметри запиту. У наступному прикладі здійснюється доступ до одного поля звичайної форми та одного завантаженого файлу:

Java

@Controller
public class FileUploadController {
    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {
        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes деякий час
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}
Kotlin

@Controller
class FileUploadController {
    @PostMapping("/form")
    fun handleFormUpload(@RequestParam("name") name: String,
                        @RequestParam("file") file: MultipartFile): String {
        if (!file.isEmpty) {
            val bytes = file.bytes
            // store the bytes деякий час
            return "redirect:uploadSuccess"
        }
        return "redirect:uploadFailure"
    }
}

Оголошення типу аргументу як List<MultipartFile> уможливлює дозвіл кількох файлів для одного і того ж імені параметра.

Якщо анотація @RequestParam оголошена як Map<String, MultipartFile> або MultiValueMap<String, MultipartFile> без імені параметра, вказаного в анотації, то Map заповнюється багатокомпонентними файлами для кожного зазначеного імені параметра.

При багатокомпонентному парсингу на Servlet 3.0 також можна оголосити javax.servlet.http.Part замість MultipartFile зі Spring як аргумент методу або типу значення колекції.

Ще можна використовувати багатокомпонентний вміст як елемент прив'язки даних до об'єкта команди. Наприклад, поле форми та файл з попереднього прикладу можуть бути полями об'єкта форми, як показано в наступному прикладі:

Java

class MyForm {
    private String name;
    private MultipartFile file;
    // ...
}
@Controller
public class FileUploadController {
    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {
        if (!form.getFile().isEmpty()) {
            byte[] bytes = form.getFile().getBytes();
            // store the bytes деякий час
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}
Kotlin

class MyForm(val name: String, val file: MultipartFile, ...)
@Controller
class FileUploadController {
    @PostMapping("/form")
    fun handleFormUpload(form: MyForm, errors: BindingResult): String {
        if (!form.file.isEmpty) {
            val bytes = form.file.bytes
            // store the bytes деякий час
            return "redirect:uploadSuccess"
        }
        return "redirect:uploadFailure"
    }
}

Багатокомпонентні запити також можуть надсилатися від небраузерних клієнтів у сценарії c 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 ...

Можна отримати доступ до частини "метаданих" за допомогою анотації @RequestParam як до String, але тобі, ймовірно, захочеться, щоб вона була десеріалізована з JSON (аналогічно анотації @RequestBody). Використовуй анотацію @RequestPart для отримання доступу до багатокомпонентного файлу після його перетворення за допомогою HttpMessageConverter:

Java

@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") MultipartFile file) {
    // ...
}
Kotlin

@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData,
        @RequestPart("file-data") file: MultipartFile): String {
    // ...
}

Ти можеш використовувати анотацію @RequestPart у комбінації з javax.validation.Valid або використовувати анотацію @Validated зі Spring, які викликають застосування стандартної Bean Validation. За замовчуванням помилки валідації призводять до появи винятку MethodArgumentNotValidException, який перетворюється на відповідь 400 (BAD_REQUEST). Як варіант, можна обробляти помилки валідації локально в контролері через аргумент Errors або BindingResult, як показано в наступному прикладі:

Java

@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
        BindingResult result) {
    // ...
}
Kotlin

@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData,
        result: BindingResult): String {
    // ...
}

@RequestBody

Анотацію @RequestBody можна використовувати для того, щоб тіло запиту можна було прочитати та десеріалізувати в Object через HttpMessageConverter. У цьому прикладі використовується аргумент з анотацією @RequestBody:

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

Опцію Message Converters у конфігурації MVC можна використовувати для конфігурування або налаштування перетворення повідомлень.

Можна використовувати анотацію @RequestBody у поєднанні з javax.validation.Valid або анотацією @Validated зі Spring, які викликають застосування стандартної Bean Validation. За замовчуванням помилки валідації призводять до появи винятку MethodArgumentNotValidException, який перетворюється на відповідь 400 (BAD_REQUEST). Як варіант, можна обробляти помилки валідації локально в контролері через аргумент Errors або BindingResult, як показано в наступному прикладі:

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

HttpEntity

Клас HttpEntity більш-менш ідентичний у плані використання анотації @RequestBody, але заснований на об'єкті-контейнері, який відкриває заголовки та тіло запиту. У наступному лістингу наведено приклад:

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

@ResponseBody

Аннотацію @ResponseBodyможна використовувати для методу, щоб повернення серіалізувалося до тіла відповіді через HttpMessageConverter. У наступному лістингу наведено приклад:

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

@ResponseBody також підтримується на рівні класу. У цьому випадку вона успадковується всіма методами контролера. Ця дія анотації @RestController, яка є нічим іншим, як метаанотацією, позначеною анотаціями @Controller та @ResponseBody.

Можна використовувати @ResponseBody з реактивними типами.

Можна використовувати опцію Message Converters у конфігурації MVC для конфігурування або налаштування перетворення повідомлень.

Можна поєднувати методи, позначені анотацією @ResponseBody, з поданнями серіалізації у форматі JSON.

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 = ...
    val etag = ...
    return ResponseEntity.ok().eTag(etag).build(body)
}

Spring MVC підтримує використання реактивного типу з одним значенням для асинхронного створення ResponseEntity, та/або реактивних типів з одним і кількома значеннями для тіла. Це дозволяє використовувати такі типи асинхронних відповідей:

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

  • Mono<ResponseEntity<T>> передбачає всі три параметри — статус відповіді, заголовки та тіло — асинхронно в пізніший момент. Це дозволяє змінювати статус відповіді та заголовки залежно від результатів асинхронної обробки запиту.

Jackson JSON

Spring передбачає підтримку бібліотеки Jackson JSON.

Подання у форматі JSON

Spring MVC забезпечує вбудовану підтримку засобу серіалізації подання Jackson's Serialization Views, що дозволяє відображати лише підмножину всіх полів об'єкта. Щоб використовувати його з методами контролера, анотованими @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("eric", "7!jd#h23")
}
class User(
        @JsonView(WithoutPasswordView::class) val username: String,
        @JsonView(WithPasswordView::class) val password: String) {
    interface WithoutPasswordView
    interface WithPasswordView : WithoutPasswordView
}
@JsonView дозволяє використовувати масив класів подання, але водночас можна задати тільки один для кожного методу контролера. Якщо потрібно активувати не одне подання, можна використовувати інтерфейс.

Якщо потрібно зробити описане вище програмно, замість оголошення анотації @JsonView огорни значення, що повертається за допомогою MappingJacksonValue і використовуй його для надання подання серіалізації:

Java
@RestController
public class UserController {
    @GetMapping("/user")
    public MappingJacksonValue getUser() {
        User user = new User("eric", "7!jd#h23");
        MappingJacksonValue value = новий MappingJacksonValue(user);
        value.setSerializationView(User.WithoutPasswordView.class);
        return value;
    }
}
Kotlin
@RestController
class UserController {
    @GetMapping("/user")
    fun getUser(): MappingJacksonValue {
        val value = MappingJacksonValue(User("eric", "7!jd#h23"))
        value.serializationView = User.WithoutPasswordView::class.java
        return value
    }
}

Для контролерів, які використовують розпізнавання подання можна додати клас подання серіалізації до моделі, як показано в наступному прикладі:

Java
@Controller
public class UserController extends AbstractController {
    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}
Kotlin
import org.springframework.ui.set
@Controller
class UserController : AbstractController() {
    @GetMapping("/user")
    fun getUser(model: Model): String {
        model["user"] = User("eric", "7!jd#h23")
        model[JsonView::class.qualifiedName] = User.WithoutPasswordView::class.java
        return "userView"
    }
}