Обработка HTTP-запросов в Spring Boot упрощается аннотациями вроде @RequestMapping. Хотя отображения лаконичнее определяются при помощи @GetMapping и @PostMapping, в основу этих аннотаций положена @RequestMapping. Расскажем, как в Spring Boot обрабатываются аннотации, включая сопоставление путей, разрешение HTTP-методов и привязку параметров. Сфокусируемся на их внутренних механизмах.
Основы @RequestMapping и специализированных аннотаций
В Spring Boot @RequestMapping — основополагающая аннотация, ею HTTP-запросы отображаются на методы обработчика в классах контроллеров. При этом указываются путь URL-адреса, HTTP-метод, заголовки и другие детали запросов, которыми активируются конкретные методы контроллера.
Пример синтаксиса:
@RestController
public class ExampleController {
@RequestMapping(value = "/example", method = RequestMethod.GET)
public String handleGetRequest() {
return "Handled GET request";
}
}
Хотя для определения отображений запросов в @RequestMapping имеется гибкая структура, для типичных HTTP-методов в Spring представили специализированные аннотации, которыми сокращается шаблонный код:
@GetMappingдляRequestMethod.GET;@PostMappingдляRequestMethod.POST;@PutMappingдляRequestMethod.PUT;@DeleteMappingдляRequestMethod.DELETE;@PatchMappingдляRequestMethod.PATCH.
Эти аннотации — синтаксические конструкции, которые предопределенными HTTP-методами, по сути, делегируются в @RequestMapping.
Пример сравнения:
// Эквивалент «@RequestMapping(method = RequestMethod.GET)»
@GetMapping("/example")
public String handleGetRequest() {
return "Handled GET request";
}
Несмотря на удобство этих специализированных аннотаций, базовый процесс обработки запросов по-прежнему управляется теми же основными компонентами, которыми контролируется @RequestMapping.
Как внутри Spring Boot выполняется отображение запросов
Когда запускается приложение Spring Boot, в нем инициируются процессы для обнаружения, регистрации и управления отображениями HTTP-запросов. Это осуществляется основными компонентами фреймворка Spring Web MVC, в частности классами RequestMappingHandlerMapping и RequestMappingHandlerAdapter. Чтобы понять, как входящие запросы соотносятся в Spring Boot с конкретными методами обработчика в классах контроллеров, разберем функционирование этих компонентов.
Обнаружение отображения запросов при запуске
Процесс отображения запросов начинается на этапе инициализации контекста приложения. Когда запускается Spring Boot, в компонентах определенных базовых пакетов разыскиваются классы, аннотированные как @Controller или @RestController. Эти классы считаются кандидатами для отображения запросов.
Обнаруженные классы проверяются в Spring на наличие методов, аннотированных как @RequestMapping, или ее специализированных вариантов: @GetMapping, @PostMapping и т. д. Обнаружение этих методов контролируется в RequestMappingHandlerMapping — центральном компоненте, который занимается отображением HTTP-запросов на методы обработчика.
RequestMappingHandlerMapping — регистрация отображений
Классом RequestMappingHandlerMapping расширяется AbstractHandlerMethodMapping — обобщенный класс для отображения HTTP-запросов на методы. На этапе запуска им выполняются такие задачи:
- Сканирование обработчиков: в контексте приложения ищутся компоненты, аннотированные как
@Controllerили@RestController. - Нахождение методов обработчика: для каждого обнаруженного контроллера выявляются методы, аннотированные как
@RequestMappingили ее синтаксическими конструкциями. - Сбор информации об отображении: создаются объекты
RequestMappingInfo, в которые инкапсулируются сведения: путь URL-адреса, HTTP-метод, заголовки, типы носителей. - Регистрация отображений: эти объекты
RequestMappingInfoзатем привязываются к соответствующим методам обработчика и, чтобы ускорить поиск при обработке запроса, сохраняются в реестре отображений.
RequestMappingInfo — это контейнер метаданных со всей необходимой информацией о направлении конкретного HTTP-запроса в метод контроллера, которая хранится в структуре данных — обычно в Map, где ключом является RequestMappingInfo, а значением — HandlerMethod.
Пример внутреннего отображения:
RequestMappingInfoдляGET /example.- Путь:
/example. - Метод:
GET. - HandlerMethod:
ExampleController.handleGetRequest().
Когда получается запрос, этим отображением в Spring быстро отыскивается корректный метод обработчика.
Стратегия сопоставления путей
Когда отображения зарегистрированы, стратегией сопоставления путей в Spring Boot определяется, каким обработчиком обрабатывать входящий запрос. По умолчанию классом AntPathMatcher здесь поддерживается простое сопоставление с образцом на основе синтаксиса в стиле Ant:
*— сопоставляется ноль или более символов в одном сегменте пути;**— сопоставляется ноль или более каталогов в пути;{variable}— обозначаются переменные пути, динамически извлекаемые из URL-адреса.
Сопоставление путей в действии
Например, в этих отображениях:
/files/*сопоставляется с/files/image.jpg;/files/**сопоставляется с/files/images/2023/photo.png;/files/{filename}сопоставляется с/files/report.pdf, при этомfilenameпривязано кreport.pdf.
Входящие запросы обрабатываются в AntPathMatcher сравнением унифицированного идентификатора ресурса запроса с зарегистрированными шаблонами, при необходимости используются совпадение с трафаретным символом и извлечение переменных пути.
Парсинг шаблонов путей в Spring 5.3+
В Spring 5.3 появился PathPatternParser — альтернатива AntPathMatcher. В этом парсере выше производительность, поддерживается дополнительный функционал вроде чувствительного к регистру сопоставления и более эффективного парсинга сложных шаблонов путей.
Разработчики переключаются на PathPatternParser, настроив свойства приложения:
spring.mvc.pathmatch.matching-strategy=path_pattern_parser
Это изменение сказывается на том, как в Spring обрабатываются и сопоставляются пути запросов, контроль над поведением при сопоставлении путей становится более детальным.
Разрешение HTTP-метода
Когда получается HTTP-запрос, в Spring Boot определяется соответствующий метод обработчика: указанный в заголовке запроса HTTP-метод проверяется и сопоставляется с зарегистрированными отображениями. В каждом зарегистрированном через RequestMappingHandlerMapping отображении содержатся сведения о разрешенных HTTP-методах, запросы направляются в корректный метод.
Если HTTP-метод в запросе совпадает с одним из методов, зарегистрированных для запрашиваемого пути, вызывается соответствующий обработчик. Если совпадения нет, в Spring возвращается ответ 405 Method Not Allowed.
Пример разрешения HTTP-метода
Рассмотрим такой контроллер:
@RestController
public class ExampleController {
@RequestMapping(value = "/example", method = RequestMethod.GET)
public String handleGetRequest() {
return "Handled GET request";
}
@RequestMapping(value = "/example", method = RequestMethod.POST)
public String handlePostRequest() {
return "Handled POST request";
}
}
Здесь для пути /example регистрируется два метода обработчика:
handleGetRequest()дляGET /example.handlePostRequest()дляPOST /example.
Теперь посмотрим, как в Spring Boot разрешаются входящие запросы:
- В
/exampleполученGET-запрос.
В Spring проверяется реестр отображений и обнаруживается, что/exampleдляGET-метода отображается наhandleGetRequest(). Поскольку имеется совпадение, в Spring запрос перенаправляется вhandleGetRequest()и возвращается ответ"Handled GET request". - В
/exampleполученPOST-запрос.
В Spring проверяются отображения и в качестве обработчика дляPOST-метода в/exampleобнаруживаетсяhandlePostRequest(), куда направляется запрос, а обратно возвращается"Handled POST request". - В
/exampleполученPUT-запрос.
В Spring разыскивается обработчик, которым путь/exampleсопоставляется с методомPUT. Поскольку такого отображения нет, в Spring возвращается 405 Method Not Allowed, то есть запрошенный HTTP-метод для этого пути не поддерживается.
Что происходит внутри
Во время запуска в Spring сканируется контекст приложения и для пути /example регистрируется два отображения: одним handleGetRequest() связывается с RequestMethod.GET, другим handlePostRequest() — с RequestMethod.POST. Когда поступает запрос, в Spring сначала проверяется соответствие пути запроса какому-либо из зарегистрированных путей. Если найдено совпадение, проверяется соответствие указанного в запросе HTTP-метода разрешенным для этого пути методам. Если и путь, и метод совпадают с зарегистрированным отображением, вызывается соответствующий метод обработчика. Если путь совпадает, а HTTP-метод — нет, в Spring возвращается ошибка 405 Method Not Allowed. Этим процессом обеспечивается обработка каждого запроса исключительно методами обработчика, явно сконфигурированными для поддержки заданных HTTP-методов, чем поддерживается точный контроль над обработкой запросов.
Комбинирование сопоставления путей и методов
На практике, чтобы для каждого запроса выявить корректный метод обработки, сопоставление путей и разрешение HTTP-методов в Spring Boot комбинируются. Процесс этот состоит из таких этапов:
- Сопоставление путей: унифицированный идентификатор ресурса запроса сопоставляется с зарегистрированными шаблонами путей.
- Сопоставление методов: для каждого совпадающего пути в Spring проверяется, соответствует ли HTTP-метод в запросе разрешенным методам для этого пути.
- Выбор оптимального соответствия: если запросу соответствует несколько обработчиков, на основе шаблона путей и HTTP-метода в Spring выбирается наиболее точное соответствие.
Рассмотрим такие отображения:
@GetMapping("/items")
public String getAllItems() { return "All items"; }
@GetMapping("/items/{id}")
public String getItem(@PathVariable String id) { return "Item: " + id; }
- Первому методу соответствует запрос в
GET /items. - Второму методу соответствует запрос в
GET /items/123сid = 123.
В Spring выбирается обработчик, который наиболее соответствует и пути, и HTTP-методу, так что запрос направляется в корректный метод.
RequestMappingHandlerAdapter — вызов метода
Когда корректный метод обработки выявлен, он вызывается в Spring Boot при помощи RequestMappingHandlerAdapter. Этот адаптер занимается подготовкой аргументов метода, вызовом метода и обработкой возвращаемого значения.
Если в RequestMappingHandlerMapping на обработчики отображаются запросы, то в RequestMappingHandlerAdapter выполняются методы обработчика и, чтобы управлять полным жизненным циклом запроса и ответа, осуществляется взаимодействие с компонентами вроде ArgumentResolvers, ReturnValueHandlers и HttpMessageConverters.
Адаптером из реестра отображений извлекается метод обработчика, разрешаются любые необходимые аргументы, через рефлексию вызывается метод. После того как метод выполнен, адаптером обрабатывается возвращаемое значение и преобразуется в соответствующий формат ответа: JSON, XML или обычный текст.
Обработка исключений и генерирование ответов
Если при отображении запросов появляются ошибки, например не найден обработчик или не поддерживается HTTP-метод, соответствующие ответы генерируются в Spring Boot механизмами обработки исключений:
- 404 Not Found: возвращается, когда для запрошенного пути не найден соответствующий обработчик;
- 405 Method Not Allowed: возвращается, когда путь совпадает, а HTTP-метод — нет;
- 400 Bad Request: возвращается при сбое привязки параметров или сбое преобразования типов.
Чтобы управлять этими исключениями и настраивать ответы на ошибки, в Spring Boot применяются методы @ExceptionHandler или глобальные классы @ControllerAdvice. Так обеспечивается согласованная обработка ошибок в приложении.
Привязка параметров и вызов метода
После того как в Spring Boot выявляется соответствующий метод обработчика для входящего запроса, следующий этап — подготовка метода к выполнению. Это привязка данных запроса к параметрам метода и управление процессом вызова. Процесс привязки очень гибкий: данные из разных частей HTTP-запроса, таких как параметры запросов, переменные путей, заголовки и текст запроса, добавляются непосредственно в аргументы метода. Рассмотрим, как в Spring Boot выполняются привязка параметров и вызов метода.
Разрешение аргументов метода
Аргументы методов в Spring Boot разрешаются интерфейсом HandlerMethodArgumentResolver, которым определяется контракт для преобразования частей HTTP-запроса в объекты Java, передаваемые в методы контроллера.
При запуске приложения в Spring Boot регистрируется набор резолверов аргументов, каждый из которых предназначен для обработки конкретных типов аннотаций и типов-параметров. Вот самые распространенные резолверы:
RequestParamMethodArgumentResolverдля обработки@RequestParamPathVariableMethodArgumentResolverдля обработки@PathVariableRequestHeaderMethodArgumentResolverдля обработки@RequestHeaderRequestBodyMethodArgumentResolverдля обработки@RequestBody
Когда HTTP-запрос направляется в метод обработчика, в RequestMappingHandlerAdapter перебираются резолверы зарегистрированных аргументов и определяется, как заполнять каждый параметр метода.
Пример разрешения аргументов
@GetMapping("/user/{id}")
public String getUser(@PathVariable int id, @RequestParam String name) {
return "User ID: " + id + ", Name: " + name;
}
В Spring данные запроса привязываются к параметрам метода различными резолверами. Резолвером PathVariableMethodArgumentResolver из пути URL-адреса извлекается значение {id} и преобразуется в int, а в RequestParamMethodArgumentResolver из URL-адреса запроса получается параметр запроса name. Извлеченные данные перед передачей в метод преобразуются обоими резолверами с помощью ConversionService в корректные типы Java.
Это поэтапный процесс:
- Проверка параметров метода
Для выявления аннотаций вроде@PathVariableи@RequestParamв Spring проверяется сигнатура метода. - Выбор резолвера
На основе аннотаций параметров и типов-параметров фреймворком выбираются соответствующие резолверы аргументов. - Извлечение и преобразование данных
Релевантные данные извлекаются резолвером из запроса и преобразуются в соответствующий тип Java благодаряConversionService. - Добавление параметров
Преобразованные значения добавляются в аргументы метода, после чего метод готов к вызову.
Привязка тела запроса с @RequestBody
Для методов, которыми обрабатывается тело запроса, в Spring Boot используется RequestBodyMethodArgumentResolver. Этим резолвером считывается полезная нагрузка входящего запроса и с помощью HttpMessageConverter преобразуется в объект Java.
Пример:
@PostMapping("/submit")
public String submitData(@RequestBody Data data) {
return "Received data: " + data.toString();
}
В RequestBodyMethodArgumentResolver обрабатывается привязка тела запроса к объекту Data. Сначала проверкой заголовка Content-Type входящего запроса определяется, как обрабатывать текст запроса, например, интерпретировать его как JSON или XML. Исходя из этого, в Spring выбирается соответствующий HttpMessageConverter. Для полезной нагрузки JSON обычно применяется MappingJackson2HttpMessageConverter с библиотекой Jackson для десериализации данных JSON в объект Data. При проблемах с десериализацией — некорректно сформированый JSON или несоответствие типов данных — в Spring выбрасывается исключение HttpMessageNotReadableException, которое чревато ответом «400 Bad Request», если не реализована пользовательская обработка исключений.
Преобразование данных и обработка типов
Преобразования типов во время привязки параметров осуществляются в Spring Boot при помощи ConversionService. При этом данные преобразуются из типичных для HTTP-запросов строковых представлений в сложные типы Java, требуемые методу обработчика.
В Spring Boot реализацией ConversionService по умолчанию является DefaultFormattingConversionService, которой поддерживаются разнообразные преобразования стандартных типов:
- Примитивные типы: строки в
int,double,booleanи т. д. - Дата и время: строки в
LocalDate,LocalDateTimeи другие классы Java Time API. - Коллекции: строки в массивы или коллекции, например преобразование разделенной запятыми строки в список
List.
Кроме того, в Spring регистрируются пользовательские конвертеры — для преобразования сложных или специфичных для предметной области типов данных.
Пример преобразования типов:
@GetMapping("/convert")
public String convertDate(@RequestParam LocalDate date) {
return "Converted date: " + date.toString();
}
В этом примере параметр запроса date=2023-10-09 автоматически преобразуется из строки в объект LocalDate благодаря зарегистрированным конвертерам ConversionService.
Проверка параметров
Чтобы проверять параметры во время привязки, Spring Boot интегрируется с Bean Validation API — JSR 380. Аннотациями @Valid или @Validated запускается автоматическая проверка параметров метода или текстов запросов на основе ограничений, определенных в классах Java.
Пример проверки параметров:
@PostMapping("/validate")
public String validateData(@RequestBody @Valid Data data) {
return "Valid data received";
}
Здесь аннотацией @Valid процесс проверки запускается сразу после привязывания тела запроса к объекту Data, но до вызова метода. С Bean Validation API, обычно поддерживаемым Hibernate Validator, в Spring проверяются любые нарушения определенных в классе Data ограничений: @NotNull, @Size или @Pattern. Если ограничение нарушено, в Spring выбрасывается исключение MethodArgumentNotValidException, что чревато ответом «400 Bad Request». Разработчики обрабатывают эти ошибки валидации пользовательским @ExceptionHandler, если требуется более конкретная обратная связь или форматирование ошибок.
Вызов метода
После привязки параметров в Spring Boot вызывается метод обработчика при помощи рефлексии, управляемой в RequestMappingHandlerAdapter.
Сначала из реестра извлекается соответствующий объект HandlerMethod, выявленный при отображении запросов. Затем, чтобы передавать методу корректные типы данных, с помощью различных резолверов и ConversionService в Spring разрешаются и преобразуются все аргументы метода.
Когда аргументы подготовлены, метод вызывается через API-интерфейсы Java Reflection. Любые исключения во время этого процесса, такие как недопустимые аргументы или ошибки времени выполнения, перехватываются и обрабатываются механизмами исключений Spring. Как только метод выполняется, возвращаемое значение обрабатывается в HandlerMethodReturnValueHandler и преобразуется в HTTP-ответ. Для сложных объектов данные в Spring при помощи HttpMessageConverters сериализуются в JSON или XML — в зависимости от запроса клиента.
Пример вызова метода и обработки ответа
@GetMapping("/status")
public ResponseEntity<String> getStatus() {
return new ResponseEntity<>("Service is running", HttpStatus.OK);
}
Когда в /status, отправляется запрос, в Spring отыскивается и вызывается метод getStatus(). Возвращаемым типом ResponseEntity указываются тело ответа и код состояния HTTP. Перед возвращением клиенту ответ в Spring обрабатывается и сериализуется.
Обработка исключений во время вызова
Если во время вызова метода возникают ошибки вроде несоответствий типов, сбоев проверки или исключений времени выполнения внутри метода обработчика, ответ в Spring Boot управляется встроенной системой обработки исключений.
- Несоответствие типов
Если параметр нельзя преобразовать в ожидаемый тип, в Spring выбрасывается исключениеMethodArgumentTypeMismatchException, что чревато ответом «400 Bad Request». - Ошибки валидации
Сбои валидации чреваты появлениемMethodArgumentNotValidException, которое тоже обрабатывается с ответом «400», если не определен пользовательский обработчик. - Необработанные исключения
Исключения времени выполнения, выбрасываемые из метода обработчика, управляются методами@ExceptionHandlerили глобальными классами@ControllerAdvice, благодаря чему разработчики настраивают ответы на ошибки для различных сценариев.
Пример обработки исключений:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<String> handleTypeMismatch(MethodArgumentTypeMismatchException ex) {
return new ResponseEntity<>("Invalid input type", HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGenericException(Exception ex) {
return new ResponseEntity<>("An error occurred", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Здесь несоответствия типов и другие исключения эффективно отлавливаются и обрабатываются, а клиентом получается информативная обратная связь.
Заключение
Система отображения запросов Spring Boot построена на хорошо структурированном процессе, при котором входящие HTTP-запросы соотносятся с корректными методами обработчика. Работа по эффективному обнаружению, регистрации и обработке запросов за аннотации вроде @RequestMapping и @GetMapping выполняется такими компонентами, как RequestMappingHandlerMapping и RequestMappingHandlerAdapter. От сопоставления путей и разрешения HTTP-методов до привязки параметров и вызова метода — каждый этап выполняется комбинированием рефлексии, преобразования данных и обработки сообщений. Благодаря этим механизмам осуществляется точное управление сложными веб-запросами, поэтому Spring Boot — надежный фреймворк для создания веб-приложений.
Читайте также:
- Механизм повторных попыток в Spring Boot: @Retryable и @Recover
- Язык выражений Spring для создания простого движка правил
- Реализация распределенной трассировки с OpenTelemetry и Spring Boot 3
Читайте нас в Telegram, VK и Дзен
Перевод статьи Alexander Obregon: The Mechanics of Request Mapping in Spring Boot




