Методы обработчика с аннотацией @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.

Кроме того, можно специальным образом обрабатывать, например, возникающее исключение MissingPathVariableException в случае, если аннотация @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.

@RequestHeader

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

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

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

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

Java
@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding, 
        @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.

@SessionAttributes

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

В следующем примере используется аннотация @SessionAttributes:

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 somewhere
            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 somewhere
            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 somewhere
            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 somewhere
            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 = new 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"
    }
}