Вы можете использовать аннотацию @RequestMapping для отображения запросов на методы контроллеров. Она имеет различные атрибуты для сопоставления по URL-адресу, HTTP-методу, параметрам запроса, заголовкамзаголовкам и типам данных. Вы можете использовать её на уровне класса для выражения общих отображений или на уровне метода для сужения до конкретного отображения конечной точки.

Существуют также специфические для HTTP-метода варианты сокращения аннотации @RequestMapping:

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

Сокращения являются кастомными аннотациями, которые указываются потому, что, пожалуй, большинство методов контроллера нужно сопоставить с конкретным HTTP-методом, а не использовать аннотацию @RequestMapping, которая по умолчанию производит сопоставление со всеми HTTP-методами. Для выражения общих отображений на уровне класса по-прежнему необходима @RequestMapping.

Следующий пример имеет отображения на уровне типов и методов:

Java
@RestController
@RequestMapping("/persons")
class PersonController {
    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}
Kotlin
@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. Например:

Java
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}
Kotlin
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
    // ...
}

Вы можете объявлять переменные URI-идентификаторов на уровне классов и методов, как показано в следующем примере:

Java
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}
Kotlin
@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", то следующий метод извлекает имя, версию и расширение файла:

Java
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
    // ...
}
Kotlin
@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 запроса, как показано в следующем примере:

Java
@PostMapping(path = "/pets", consumes = "application/json") 
public void addPet(@RequestBody Pet pet) {
    // ...
}
  1. Использование атрибута consumes для сужения диапазона отображения по типу содержимого.
Kotlin
@PostMapping("/pets", consumes = ["application/json"]) 
fun addPet(@RequestBody pet: Pet) {
    // ...
}
  1. Использование атрибута consumes для сужения диапазона отображения по типу содержимого.

Атрибут consumes также поддерживает выражения отрицания – например, !text/plain означает любой тип содержимого, отличный от text/plain.

Можно объявлять общий атрибут consumes на уровне класса. Однако, в отличие от большинства других атрибутов отображения запросов, при использовании на уровне класса, а атрибут consumes ,на уровне метода скорее переопределяет, а не расширяет объявление на уровне класса.

MediaType предоставляет константы для часто используемых типов сред передачи данных, таких как APPLICATION_JSON_VALUE и APPLICATION_XML_VALUE.

Производимые типы данных

Можно сузить диапазон отображения запросов на основе заголовка запроса Accept и списка типов содержимого, которые производит метод контроллера, как показано в следующем примере:

Java
@GetMapping(path = "/pets/{petId}", produces = "application/json") 
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}
  1. Использование атрибута produces для сужения диапазона отображения по типу содержимого.
Kotlin
@GetMapping("/pets/{petId}", produces = ["application/json"]) 
@ResponseBody
fun getPet(@PathVariable petId: String): Pet {
    // ...
}
  1. Использование атрибута produces для сужения диапазона отображения по типу содержимого.

Тип среды передачи данных может определять набор символов. Поддерживаются отрицаемые выражения – например, !text/plain означает любой тип содержимого, отличный от "text/plain".

Можно объявлять общий атрибут produces на уровне класса. Однако, в отличие от большинства других атрибутов отображения запросов, при использовании на уровне класса атрибут produces на уровне метода скорее переопределяет, а не расширяет объявление на уровне класса.

MediaType предоставляет константы для часто используемых типов сред передачи данных, таких как APPLICATION_JSON_VALUE и APPLICATION_XML_VALUE.

Параметры, заголовки

Можно сузить диапазон отображения запросов на основе условий параметров запроса. Вы можете проверить наличие параметра запроса(myParam), его отсутствие (!myParam) или определенное значение(myParam=myValue). В следующем примере показано, как проверить наличие определенного значения:

Java
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}
  1. Проверка того, равен ли myParam значению myValue.
Kotlin
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) 
fun findPet(@PathVariable petId: String) {
    // ...
}
  1. Проверка того, равен ли myParam значению myValue.

То же самое можно использовать и с условиями заголовка запроса, как показано в следующем примере:

Java
@GetMapping(path = "/pets", headers = "myHeader=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}
  1. Проверка того, равен ли myHeader значению myValue.
Kotlin
@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-адресами. В следующем примере регистрируется метод обработчика:

Java
@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); 
    }
}
  1. Внедряем целевой обработчик и отображение обработчика для контроллеров.
  2. Подготавливаем метаданные отображения запроса.
  3. Получаем метод обработчика.
  4. Добавляем регистрацию.
Kotlin
@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) 
    }
}
  1. Внедряем целевой обработчик и отображение обработчика для контроллеров.
  2. Подготавливаем метаданные отображения запроса.
  3. Получаем метод обработчика.
  4. Добавляем регистрацию.