Вы можете использовать аннотацию @RequestMapping
для отображения запросов на методы контроллеров. Она имеет различные атрибуты для сопоставления по URL-адресу, HTTP-методу, параметрам запроса, заголовкамзаголовкам и типам данных. Вы можете использовать её на уровне класса для выражения общих отображений или на уровне метода для сужения до конкретного отображения конечной точки.
Существуют также специфические для HTTP-метода варианты сокращения аннотации @RequestMapping
:
-
@GetMapping
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
@PatchMapping
Сокращения являются кастомными аннотациями, которые указываются потому, что, пожалуй, большинство методов контроллера нужно сопоставить с конкретным HTTP-методом, а не использовать аннотацию @RequestMapping
, которая по умолчанию производит сопоставление со всеми HTTP-методами. Для выражения общих отображений на уровне класса по-прежнему необходима @RequestMapping
.
Следующий пример имеет отображения на уровне типов и методов:
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
fun getPerson(@PathVariable id: Long): Person {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun add(@RequestBody person: Person) {
// ...
}
}
Шаблоны URI-идентификаторов
Методы, аннотированные @RequestMapping
, можно отобразить с помощью шаблонов URL-адресов. Есть две альтернативы:
-
PathPattern
- предварительно парсированный шаблон, сопоставленный с путем URL-адреса, также предварительно парсированный какPathContainer
. Это решение, разработанное для использования в Интернет-среде, эффективно работает с параметрами кодировки и пути, а также обеспечивает эффективное сопоставление. -
AntPathMatcher
- сопоставление строковых шаблонов со строковым путем. Это оригинальное решение также используется в конфигурации Spring для выбора ресурсов в classpath, в файловой системе и других местах. Оно менее эффективно, а ввод строкового пути затрудняет эффективную работу с кодировкой и другими проблемами с URL-адресами.
PathPattern
– это рекомендуемое решение для веб-приложений, и это единственный вариант при работе в Spring WebFlux. До версии 5.3 AntPathMatcher
был единственным вариантом при работе в Spring MVC и остается им по умолчанию. Однако PathPattern
можно активировать в конфигурации MVC.
PathPattern
поддерживает тот же синтаксис шаблонов, что и AntPathMatcher
. Кроме того, он также поддерживает шаблон захвата, например {*spring}
, для сопоставления 0 или более сегментов пути в конце пути. PathPattern
также ограничивает использование знака **
для сопоставления нескольких сегментов пути таким образом, что он разрешен только в конце шаблона. Это позволяет избежать случаев неоднозначности при выборе наилучшего шаблона согласования для данного запроса. Полный синтаксис шаблонов приведен в разделах, посвященных PathPattern и AntPathMatcher.
Некоторые примеры шаблонов:
-
"/resources/ima?e.png"
– сопоставление с одним символом в сегменте пути -
"/resources/*.png"
– сопоставление с ноль или более символами в сегменте пути -
"/resources/**"
– сопоставление с несколькими сегментами пути -
"/projects/{project}/versions"
– сопоставление с сегментом пути и запись его в качестве переменной -
"/projects/{project:[a-z]+}/versions"
– сопоставление и захват переменной с помощью регулярного выражения
К захваченным переменным URI-идентификаторов можно получить доступ с помощью аннотации @PathVariable
. Например:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
// ...
}
Вы можете объявлять переменные URI-идентификаторов на уровне классов и методов, как показано в следующем примере:
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
@Controller
@RequestMapping("/owners/{ownerId}")
class OwnerController {
@GetMapping("/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
// ...
}
}
Переменные URI-идентификаторов автоматически преобразуются в соответствующий тип, или же генерируется TypeMismatchException
. Простые типы(int
, long
, Date
и так далее) поддерживаются по умолчанию, но можно зарегистрировать средства поддержки любого другого типа данных. См. раздел "Преобразование типов" и "DataBinder"
.
Можно явным образом присвоить имена переменным URI-идентификаторов (например, @PathVariable("customId")
), но также можете и не указывать эти сведения, если имена одинаковы, а ваш код компилируется с использованием отладочной информации или с флагом компилятора -parameters
на Java 8.
Синтаксис {varName:regex}
объявляет переменную URI-идентификатора с помощью регулярного выражения, которое имеет синтаксис {varName:regex}
. Например, если дан URL-адрес "/spring-web-3.0.5.jar"
, то следующий метод извлекает имя, версию и расширение файла:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
// ...
}
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun handle(@PathVariable name: String, @PathVariable version: String, @PathVariable ext: String) {
// ...
}
Шаблоны путей URI-идентификатора также могут содержать встроенные плейсхолдеры ${…}
, которые распознаются при запуске с помощью PropertySourcesPlaceholderConfigurer
в локальных, системных источниках свойств, источниках свойств окружения и др. Можно использовать это, например, для параметризации базового URL-адреса на основе некоторой внешней конфигурации.
Сравнение шаблонов
Если URL-адресу соответствуют несколько шаблонов, необходимо выбрать наиболее подходящий. Это делается одним из следующих способов в зависимости от того, разрешен ли синтаксически парсированный PathPattern
к использованию или нет:
И то, и другое помогает сортировать шаблоны, причем более конкретные из них находятся сверху. Шаблон является менее конкретным, если в нем меньше переменных URI-идентификаторов (считается за 1), одиночных подстановочных знаков (считается за 1) и двойных подстановочных знаков (считается за 2). При равным показателях выбирается более длинный шаблон. При одинаковых показателях и длине выбирается шаблон, в котором больше переменных URI-идентификаторов, чем подстановочных знаков.
Шаблон отображения по умолчанию(/**
) исключается из подсчета и всегда сортируется последним. Кроме того, шаблоны с префиксами (такие как /public/**
) считаются менее конкретными, чем другие шаблоны, не содержащие двойных подстановочных знаков.
Для получения полной информации перейдите по указанным выше ссылкам на описания компараторов шаблонов.
Суффиксальное совпадение
Начиная с версии 5.3, Spring MVC по умолчанию больше не выполняет сопоставление шаблонов с суффиксами .*
, если контроллер, сопоставленный с /person
, также неявно сопоставляется с /person.*
. Как следствие, расширения путей больше не используются для интерпретации типа запрашиваемого содержимого для ответа – например, /person.pdf
, /person.xml
и так далее.
Использование расширений файлов таким способом было необходимо, когда браузеры отправляли заголовки Accept
, которые было трудно интерпретировать последовательно. В настоящее время в этом уже нет необходимости, и использование заголовка Accept
должно быть предпочтительным выбором.
Со временем использование расширений имен файлов оказалось проблематичным по целому ряду причин. Это может вызвать неоднозначность при наложении с использованием переменных URI-идентификаторов, параметров пути и кодировки URI-идентификаторов. Обоснование правильности авторизации и безопасности на основе URL (подробнее см. следующий раздел) также усложняется.
Чтобы полностью отключить использование расширений путей в версиях до 5.3, установите следующее:
-
useSuffixPatternMatching(false)
-
favorPathExtension(false)
Наличие способа запроса типов содержимого, отличного от заголовка "Accept"
, может быть полезным, например, при вводе URL-адреса в браузере. Безопасной альтернативой расширению пути является использование стратегии параметров запроса. Если вам необходимо использовать расширения файлов, подумайте о том, чтобы ограничить их списком явно зарегистрированных расширений через свойство mediaTypes
для ContentNegotiationConfigurer.
Суффиксальное совпадение и RFD
Вектор атаки отраженной загрузки файлов (Reflected File Download/RFD) похож на XSS тем, что он основывается на том, что вводные данные запроса (например, параметр запроса и переменная URI-идентификатора) отражаются в ответе. Однако вместо вставки JavaScript в HTML, вектор атаки RFD основан на переключении браузера на выполнение загрузки и обработке ответа как исполняемого скрипта при последующем двойном щелчке.
В Spring MVC методы, помеченные аннотацией @ResponseBody
, и методы ResponseEntity
находятся в зоне риска, поскольку могут визуализировать различные типы контента, которые клиенты могут запрашивать через расширения путей URL-адреса. Отключение суффиксального сопоставления шаблонов и использование расширений пути для согласования содержимого снижают риск, но не являются достаточными для предотвращения RFD-атак.
Для предотвращения RFD-атак перед визуализацией тела ответа Spring MVC добавляет заголовок Content-Disposition:inline;filename=f.txt
, чтобы предложить фиксированный и безопасный файл загрузки. Это делается только в том случае, если путь URL-адреса содержит расширение файла, которое не разрешено как безопасное и не зарегистрировано явным образом для согласования содержимого. Однако это может иметь побочные эффекты, если URL-адреса вводятся непосредственно в браузере.
Многие распространенные расширения путей по умолчанию разрешены как безопасные. Приложения со кастомными реализациями HttpMessageConverter
могут явно регистрировать расширения файлов для согласования содержимого, чтобы избежать добавления заголовка Content-Disposition
для этих расширений.
Дополнительные рекомендации, связанные с RFD, см. в разделе "CVE-2015-5211".
Потребляемые типы данных
Можно сузить диапазон отображения запросов на основе Content-Type
запроса, как показано в следующем примере:
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
- Использование атрибута
consumes
для сужения диапазона отображения по типу содержимого.
@PostMapping("/pets", consumes = ["application/json"])
fun addPet(@RequestBody pet: Pet) {
// ...
}
- Использование атрибута
consumes
для сужения диапазона отображения по типу содержимого.
Атрибут consumes
также поддерживает выражения отрицания – например, !text/plain
означает любой тип содержимого, отличный от text/plain
.
Можно объявлять общий атрибут consumes
на уровне класса. Однако, в отличие от большинства других атрибутов отображения запросов, при использовании на уровне класса, а атрибут consumes
,на уровне метода скорее переопределяет, а не расширяет объявление на уровне класса.
MediaType
предоставляет константы для часто используемых типов сред передачи данных, таких как APPLICATION_JSON_VALUE
и APPLICATION_XML_VALUE
.Производимые типы данных
Можно сузить диапазон отображения запросов на основе заголовка запроса Accept
и списка типов содержимого, которые производит метод контроллера, как показано в следующем примере:
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
- Использование атрибута
produces
для сужения диапазона отображения по типу содержимого.
@GetMapping("/pets/{petId}", produces = ["application/json"])
@ResponseBody
fun getPet(@PathVariable petId: String): Pet {
// ...
}
- Использование атрибута
produces
для сужения диапазона отображения по типу содержимого.
Тип среды передачи данных может определять набор символов. Поддерживаются отрицаемые выражения – например, !text/plain
означает любой тип содержимого, отличный от "text/plain".
Можно объявлять общий атрибут produces
на уровне класса. Однако, в отличие от большинства других атрибутов отображения запросов, при использовании на уровне класса атрибут produces
на уровне метода скорее переопределяет, а не расширяет объявление на уровне класса.
MediaType
предоставляет константы для часто используемых типов сред передачи данных, таких как APPLICATION_JSON_VALUE
и APPLICATION_XML_VALUE
.Параметры, заголовки
Можно сузить диапазон отображения запросов на основе условий параметров запроса. Вы можете проверить наличие параметра запроса(myParam
), его отсутствие (!myParam
) или определенное значение(myParam=myValue
). В следующем примере показано, как проверить наличие определенного значения:
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
- Проверка того, равен ли
myParam
значениюmyValue
.
@GetMapping("/pets/{petId}", params = ["myParam=myValue"])
fun findPet(@PathVariable petId: String) {
// ...
}
- Проверка того, равен ли
myParam
значениюmyValue
.
То же самое можно использовать и с условиями заголовка запроса, как показано в следующем примере:
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
- Проверка того, равен ли
myHeader
значениюmyValue
.
@GetMapping("/pets", headers = ["myHeader=myValue"])
fun findPet(@PathVariable petId: String) {
// ...
}
Content-Type
и Accept
с условием заголовков, но лучше вместо этого использовать consumes и produces.HTTP-методы HEAD, OPTIONS
@GetMapping
(и @RequestMapping(method=HttpMethod.GET)
) прозрачно поддерживают HTTP-метод HEAD для отображения запросов. Методы контроллера не нужно менять. Функция-обёртка ответа, применяемая в javax.servlet.http.HttpServlet
, обеспечивает установку заголовка Content-Length
на количество записанных байт (без фактической записи в ответ).
@GetMapping
(и @RequestMapping(method=HttpMethod.GET)
) неявно отображены на HTTP-метод HEAD и поддерживают его. Запрос по HTTP-методу HEAD обрабатывается так же, как и по HTTP-методу GET, за исключением того, что вместо записи тела подсчитывается количество байт и устанавливается заголовок Content-Length
.
По умолчанию HTTP-метод OPTIONS обрабатывается путем установки заголовка ответа Allow
в список HTTP-методов, перечисленных во всех методах, помеченных аннотацией@RequestMapping
, которые имеют соответствующие шаблоны URL-адресов.
В случае аннотации @RequestMapping
без объявления HTTP-методов, заголовок Allow
устанавливается в GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS
. Методы контроллера всегда должны объявлять поддерживаемые HTTP-методы (например, с помощью вариантов, специфичных для HTTP-методов: @GetMapping
, @PostMapping
и другие).
Вы можете явно отобразить метод, аннотированный @RequestMapping
, на HTTP-метод HEAD и HTTP-метод OPTIONS, но в рядовом случае это делать не обязательно.
Кастомные аннотации
Spring MVC поддерживает использование составных аннотаций для отображения запросов. Это аннотации, которые сами являются мета-аннотациями @RequestMapping
и составлены для повторного объявления подмножества (или всех) атрибутов, помеченных аннотацией @RequestMapping
, с более узкой, более конкретной целью.
@GetMapping
, @PostMapping
, @PutMapping
, @DeleteMapping
и @PatchMapping
являются примерами составных аннотаций. Они предусматриваются потому, что, вероятно, большинство методов контроллера нужно будет сопоставить с конкретным HTTP-методом вместо использования аннотации @RequestMapping
, которая по умолчанию сопоставляется со всеми HTTP-методами. Если вам требуется пример составных аннотаций, посмотрите, как они объявляются.
Spring MVC также поддерживает кастомные атрибуты отображения запросов с кастомно заданной логикой отображения запросов. Это более расширенный вариант, который требует создания подкласса RequestMappingHandlerMapping
и переопределения метода getCustomMethodCondition
, в котором можно проверить пользовательский атрибут и вернуть свое собственное RequestCondition
.
Явная регистрация
Вы можете программно регистрировать методы обработчика, которые можно использовать для динамической регистрации или при более сложных случаях, например, если имеются разные экземпляры одного и того же обработчика под разными URL-адресами. В следующем примере регистрируется метод обработчика:
@Configuration
public class MyConfig {
@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler)
throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
.paths("/user/{id}").methods(RequestMethod.GET).build();
Method method = UserHandler.class.getMethod("getUser", Long.class);
mapping.registerMapping(info, handler, method);
}
}
- Внедряем целевой обработчик и отображение обработчика для контроллеров.
- Подготавливаем метаданные отображения запроса.
- Получаем метод обработчика.
- Добавляем регистрацию.
@Configuration
class MyConfig {
@Autowired
fun setHandlerMapping(mapping: RequestMappingHandlerMapping, handler: UserHandler) {
val info = RequestMappingInfo.paths("/user/{id}").methods(RequestMethod.GET).build()
val method = UserHandler::class.java.getMethod("getUser", Long::class.java)
mapping.registerMapping(info, handler, method)
}
}
- Внедряем целевой обработчик и отображение обработчика для контроллеров.
- Подготавливаем метаданные отображения запроса.
- Получаем метод обработчика.
- Добавляем регистрацию.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ