Методы обработчика с аннотацией @RequestMapping
имеют гибкую сигнатуру и могут выбирать из ряда поддерживаемых аргументов и возвращаемых значений метода контроллера.
Аргументы метода
В следующей таблице описаны поддерживаемые аргументы метода контроллера. Реактивные типы не поддерживаются ни для каких аргументов.
Аргумент java.util.Optional
из JDK 8 поддерживается в качестве аргумента метода в сочетании с аннотациями, имеющими атрибут required
(например, @RequestParam
, @RequestHeader
и другие), и эквивалентен required=false
.
Аргумент метода контроллера | Описание |
---|---|
|
Обеспечивает типизированный доступ к параметрам запроса и атрибутам запроса и сессии, без прямого использования Servlet API. |
|
Предназначен для выбора любого конкретного типа запроса или ответа – например, |
|
Обеспечивает наличие сессии. Как следствие, такой аргумент никогда не является |
|
API проталкивающего средства сборки Servlet 4.0 для программного проталкивания (передачи) ресурсов HTTP/2. Обратите внимание, что, согласно спецификации Servlet, внедряемый экземпляр |
|
Текущий аутентифицированный пользователь – возможно, конкретный класс реализации Обратите внимание, что этот аргумент не распознается ускоренно, если имеет аннотацию, предназначенную для того, чтобы позволить кастомному распознавателю распознать его, прежде чем вернуться к распознаванию по умолчанию через |
|
HTTP-метод запроса. |
|
Текущие региональные настройки запроса, определяемые наиболее конкретным доступным |
|
Часовой пояс, связанный с текущим запросом, определенный |
|
Предназначен для обеспечения доступа к сырому телу запроса в том виде, в котором оно представлено в Servlet API. |
|
Предназначен для обеспечения доступа к сырому телу ответа, открытому Servlet API. |
|
Предназначен для обеспечения доступа к переменным шаблона URI-идентификаторов. |
|
Предназначен для обеспечения доступа к парам "имя-значение" в сегментах пути URI-идентификаторов. |
|
Предназначен для обеспечения доступа к параметрам запроса сервлета, включая многокомпонентные файлы. Значения параметров преобразуются в объявленный тип аргументов метода. Обратите внимание, что использование аннотации |
|
Предназначен для обеспечения доступа к заголовкам запросов. Значения заголовков преобразуются в объявленный тип аргумента метода. |
|
Предназначен для обеспечения доступа к cookie. Значения cookie преобразуются в объявленный тип аргумента метода. |
|
Для доступа к телу HTTP-запроса. Содержимое тела преобразуется в объявленный тип аргумента метода с помощью реализаций |
|
Предназначен для обеспечения доступа к заголовкам и телу запроса. Тело преобразуется с помощью |
|
Предназначен для обеспечения доступа к компоненту в запросе |
|
Предназначены для обеспечения доступа к модели, которая используется в HTML-контроллерах и отображается в шаблонах как часть визуализации представления. |
|
Задает атрибуты, которые будут использоваться в случае перенаправления (то есть будут добавлены к строке запроса), flash атрибуты, которые будут временно храниться до поступления запроса после перенаправления. |
|
Для доступа к существующему атрибуту в модели (экземпляр которого создается, если он отсутствует) с привязкой данных и валидацией. Обратите внимание, что использовать аннотацию |
|
Предназначен для обеспечения доступа к ошибкам валидации и привязки данных для объекта команды (то есть аргумента с аннотацией |
|
Предназначен для пометки завершения обработки формы, что вызывает очистку атрибутов сессии, объявленных через аннотацию |
|
Предназначен для подготовки URL-адреса, связанного с хостом, портом, схемой, контекстным путем текущего запроса и литеральным компонентом отображения сервлета. |
|
Предназначен для обеспечения доступа к любому атрибуту сессии, в отличие от атрибутов модели, хранящихся в сессии в результате объявления аннотации |
|
Предназначен для обеспечения доступа к атрибутам запроса. |
Любой другой аргумент |
Если аргумент метода не соответствует ни одному из предыдущих значений в этой таблице и является простым типом (как определено BeanUtils#isSimpleProperty), он разрешается как |
Возвращаемые значения
В следующей таблице описаны поддерживаемые возвращаемые значения метода контроллера. Реактивные типы поддерживаются для всех возвращаемых значений.
Возвращаемое значение метода контроллера | Описание |
---|---|
|
Возвращаемое значение преобразуется через реализацию |
|
Возвращаемое значение, задающее полный ответ (включая HTTP-заголовки и тело), преобразуется через реализацию |
|
Предназначено для возврата ответа с заголовками и без тела. |
|
Имя представления, которое должно быть распознано с помощью реализаций |
|
Экземпляр |
|
Атрибуты, добавляемые к неявной модели, с именем представления, неявно определяемым через |
|
Атрибут, добавляемый к модели, имя представления которого неявно определяется через Обратите внимание, что аннотация |
|
Используемые атрибуты представления и модели и, при необходимости, статус ответа. |
|
Считается, что метод с возвращаемым типом Если ни одно из вышеперечисленных значений не верно, возвращаемый тип |
|
Производит любое из предыдущих возвращаемых значений асинхронно из любого потока – например, в результате какого-либо события или обратного вызова. |
|
Производит любое из вышеперечисленных возвращаемых значений асинхронно в потоке, управляемом Spring MVC. |
|
Более удобная альтернатива |
|
Асинхронно порождает поток объектов для записи в ответ с помощью реализаций |
|
Асинхронная запись в |
Реактивные типы - Reactor, RxJava, или другие через |
Альтернатива В потоковых сценариях (например, |
Любое другое возвращаемое значение |
Любое возвращаемое значение, которое не соответствует ни одному из предыдущих значений в этой таблице и является |
Преобразование типа
Некоторые аннотированные аргументы метода контроллера, представляющие ввод запроса на основе 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-идентификатора для маскировки содержимого такой матричной переменной и обеспечения успешного сопоставления запроса независимо от порядка и наличия матричных переменных. В следующем примере используется матричная переменная:
// 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
}
// 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
}
Матричная переменная может быть определена как необязательная и иметь значение по умолчанию, как это показано в следующем примере:
// 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]
}
Обратите внимание, что необходимо активировать использование матричных переменных. В конфигурации MVC на Java необходимо установить UrlPathHelper
при removeSemicolonContent=false
через Path Matching. В пространстве имен MVC на XML можно установить <mvc:annotation-driven enable-matrix-variables="true"/>
.
@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
для привязкиpetId
.
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"
}
// ...
}
- Использование аннотации
@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
:
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
- Получаем значения заголовка
Accept-Encoding
. - Получаем значения заголовка
Keep-Alive
.
@GetMapping("/demo")
fun handle(
@RequestHeader("Accept-Encoding") encoding: String,
@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
JSESSIONID
.
@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) {
//...
}
- Получение значения cookie
JSESSIONID
.
Если тип параметра целевого метода не является String
, преобразование типов применяется автоматически.
@ModelAttribute
Аннотацию @ModelAttribute
можно использовать для аргумента метода, чтобы получить доступ к атрибуту из модели или создать его экземпляр, если он отсутствует. На атрибут модели также накладываются значения из параметров запроса сервлета по протоколу HTTP, имена которых совпадают с именами полей. Это называется привязыванием данных, и оно избавляет от необходимости парсить и преобразовывать отдельные параметры запроса и поля форм. В следующем примере показано, как это сделать:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) {
// method logic...
}
@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
из хранилища данных:
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
// ...
}
@PutMapping("/accounts/{account}")
fun save(@ModelAttribute("account") account: Account): String {
// ...
}
После получения экземпляра атрибута модели применяется привязка данных. Класс WebDataBinder
сопоставляет имена параметров запроса сервлета (параметры запроса и поля формы) с именами полей целевого объекта. Согласованные поля заполняются после применения преобразования типа, где это необходимо.
Привязка данных может привести к ошибкам. По умолчанию возникает исключение BindException
. Однако для проверки таких ошибок в методе контроллера можно добавить аргумент BindingResult
непосредственно рядом с аннотацией @ModelAttribute
, как показано в следующем примере:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
- Добавление
BindingResult
рядом с аннотацией@ModelAttribute
.
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String {
if (result.hasErrors()) {
return "petForm"
}
// ...
}
- Добавление
BindingResult
рядом с аннотацией@ModelAttribute
.
В некоторых случаях может понадобиться доступ к атрибуту модели без привязки к данным. Для таких случаев можно внедрить Model
в контроллер и обращаться к ней напрямую или, как вариант, установить @ModelAttribute(binding=false)
, как показано в следующем примере:
@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) {
// ...
}
- Настройка
@ModelAttribute(binding=false)
.
@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 {
// ...
}
- Настройка
@ModelAttribute(binding=false)
.
Вы можете автоматически применять валидацию после привязки данных, добавив аннотацию javax.validation.Valid
или аннотацию @Validated
из Spring. В следующем примере показано, как это сделать:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
- Валидируем экземпляра
Pet
.
@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
:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
- Использование аннотации
@SessionAttributes
.
@Controller
@SessionAttributes("pet")
class EditPetForm {
// ...
}
- Использование аннотации
@SessionAttributes
.
При первом запросе, когда атрибут модели с именем pet
добавляется к модели, он автоматически продвигается и сохраняется в HTTP-сессии сервлета. Он остается там до тех пор, пока другой метод контроллера не использует аргумент метода SessionStatus
для очистки хранилища, как показано в следующем примере:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
// ...
}
status.setComplete();
// ...
}
}
- Хранение значения
Pet
в сессии сервлета. - Очистка значения
Pet
из сессии сервлета.
@Controller
@SessionAttributes("pet")
class EditPetForm {
// ...
@PostMapping("/pets/{id}")
fun handle(pet: Pet, errors: BindingResult, status: SessionStatus): String {
if (errors.hasErrors()) {
// ...
}
status.setComplete()
// ...
}
}
- Хранение значения
Pet
в сессии сервлета. - Очистка значения
Pet
из сессии сервлета.
@SessionAttribute
Если требуется доступ к заранее существующим атрибутам сессии, которые управляются глобально (то есть вне контроллера – например, фильтром) и могут присутствовать или отсутствовать, то можно использовать аннотацию @SessionAttribute
для параметра метода, как показано в следующем примере:
@RequestMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
- Использование аннотации
@SessionAttribute
.
@RequestMapping("/")
fun handle(@SessionAttribute user: User): String {
// ...
}
Для случаев использования, требующих добавления или удаления атрибутов сессии, рассмотрите возможность внедрения org.springframework.web.context.request.WebRequest
или javax.servlet.http.HttpSession
в метод контроллера.
Для временного хранения атрибутов модели в сессии как части рабочего процесса контроллера, рассмотрите возможность использования аннотации @SessionAttributes
.
@RequestAttribute
Подобно аннотации @SessionAttribute
, можно использовать аннотации @RequestAttribute
для обеспечения доступа к уже существующим атрибутам запроса, созданным ранее (например, к Filter
или HandlerInterceptor
сервлетов):
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
- Использование аннотации
@RequestAttribute
.
@GetMapping("/")
fun handle(@RequestAttribute client: Client): String {
// ...
}
- Использование аннотации
@RequestAttribute
.
Атрибуты перенаправления
По умолчанию все атрибуты модели считаются представленными в форме шаблона URI-идентификаторов в URL-адресе перенаправления. Из оставшихся атрибутов те, которые являются примитивными типами или коллекциями или массивами примитивных типов, автоматически добавляются в качестве параметров запроса.
Добавление атрибутов примитивных типов в качестве параметров запроса может быть требуемым результатом, если экземпляр модели был подготовлен специально под перенаправление. Однако в аннотированных контроллерах модель может содержать дополнительные атрибуты, добавленные для целей визуализации (например, значения выпадающих полей). Чтобы избежать появления таких атрибутов в URL-адресе, метод, помеченный аннотацией @RequestMapping
, может объявить аргумент типа RedirectAttributes
и использовать его для задания точных атрибутов, которые должны быть доступны RedirectView
. Если метод выполняет перенаправление, используется содержимое RedirectAttributes
. В противном случае используется содержимое модели.
RequestMappingHandlerAdapter
предоставляет флаг ignoreDefaultModelOnRedirect
, который можно использовать, чтобы указать, что содержимое Model
по умолчанию ни в коем случае не следует использовать при перенаправлении методом контроллера. Вместо этого метод контроллера должен объявлять атрибут типа RedirectAttributes
или же, если он этого не делает, то никакие атрибуты не следует передавать RedirectView
. И в пространстве имен MVC, и в конфигурации MVC на Java этот флаг установлен в false
, чтобы сохранить обратную совместимость. Однако для новых приложений мы рекомендуем установить значение true
.
Обратите внимание, что переменные шаблона URI-идентификаторов из настоящего запроса автоматически становятся доступными при расширении URL-адреса перенаправления, поэтому явно добавлять их через Model
или RedirectAttributes
не требуется. В следующем примере показано, как определить перенаправление:
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
@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-адрес.
Многокомпонентность
После активации MultipartResolver
содержимое POST-запросов с multipart/form-data
парсится и становится доступным как обычные параметры запроса. В следующем примере осуществляется доступ к одному полю обычной формы и одному загруженному файлу:
@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";
}
}
@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 заполняется многокомпонентными файлами для каждого заданного имени параметра.
javax.servlet.http.Part
вместо MultipartFile
из Spring в качестве аргумента метода или типа значения коллекции.Еще можно использовать многокомпонентное содержимое как элемент привязки данных к объекту команды. Например, поле формы и файл из предыдущего примера могут быть полями объекта формы, как показано в следующем примере:
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";
}
}
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:
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
@RequestPart("file-data") MultipartFile file) {
// ...
}
@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
, как показано в следующем примере:
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
BindingResult result) {
// ...
}
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData,
result: BindingResult): String {
// ...
}
@RequestBody
Аннотацию @RequestBody
можно использовать для того, чтобы тело запроса можно было прочитать и десериализовать в Object
через HttpMessageConverter
. В следующем примере используется аргумент с аннотацией @RequestBody
:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
@PostMapping("/accounts")
fun handle(@RequestBody account: Account) {
// ...
}
Опцию Message Converters в конфигурации MVC можно использовать для конфигурирования или настройки преобразования сообщений.
Можно использовать аннотацию @RequestBody
в сочетании с javax.validation.Valid
или аннотацией @Validated
из Spring, которые вызывают применение стандартной Bean Validation. По умолчанию ошибки валидации приводят к появлению исключения MethodArgumentNotValidException
, которое превращается в ответ 400 (BAD_REQUEST). Как вариант, можно обрабатывать ошибки валидации локально в контроллере через аргумент Errors
или BindingResult
, как показано в следующем примере:
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
// ...
}
@PostMapping("/accounts")
fun handle(@Valid @RequestBody account: Account, result: BindingResult) {
// ...
}
HttpEntity
Класс HttpEntity
более или менее идентичен в плане использования аннотации @RequestBody
, но основан на объекте-контейнере, который открывает заголовки и тело запроса. В следующем листинге приведен пример:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
@PostMapping("/accounts")
fun handle(entity: HttpEntity<Account>) {
// ...
}
@ResponseBody
Аннотацию @ResponseBody
можно использовать для метода, чтобы возврат сериализовался в тело ответа через HttpMessageConverter. В следующем листинге приведен пример:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
@GetMapping("/accounts/{id}")
@ResponseBody
fun handle(): Account {
// ...
}
@ResponseBody
также поддерживается на уровне класса, в этом случае она наследуется всеми методами контроллера. Это действие аннотации @RestController
, которая является ничем иным, как мета-аннотацией, помеченной аннотациями @Controller
и @ResponseBody.
Вы можете использовать @ResponseBody
с реактивными типами.
Вы можете использовать опцию Message Converters в конфигурации MVC для конфигурирования или настройки преобразования сообщений.
Можно комбинировать методы, помеченные аннотацией @ResponseBody
, с представлениями сериализации в формате JSON.
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 = ...
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 для активации класса представления сериализации, как показано в следующем примере:
@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("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
и используйте его для предоставления представления сериализации:
@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;
}
}
@RestController
class UserController {
@GetMapping("/user")
fun getUser(): MappingJacksonValue {
val value = MappingJacksonValue(User("eric", "7!jd#h23"))
value.serializationView = User.WithoutPasswordView::class.java
return value
}
}
Для контроллеров, которые используют распознавание представлений, можно добавить класс представления сериализации в модель, как показано в следующем примере:
@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";
}
}
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"
}
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ