Как Spring MVC обрабатывает входящий HTTP-запрос?

Ответ

Обработка запроса в Spring MVC представляет собой цепочку (pipeline) компонентов, известную как Request Processing Pipeline. Вот ключевые этапы:

1. Запрос поступает в DispatcherServlet: DispatcherServlet — это Front Controller, единая точка входа для всех запросов. Он наследуется от HttpServlet и координирует весь процесс.

2. Поиск цепочки HandlerMapping: DispatcherServlet опрашивает список HandlerMapping-бинов, чтобы определить, какой контроллер (@Controller) (или HandlerMethod) должен обработать запрос, основываясь на URL (@RequestMapping).

3. Выполнение цепочки HandlerInterceptor: Если для найденного обработчика сконфигурированы HandlerInterceptor-ы, вызываются их методы preHandle(). Они могут выполнить проверки (логирование, аутентификацию) и прервать обработку.

4. Адаптация и вызов обработчика (HandlerAdapter): DispatcherServlet находит подходящий HandlerAdapter (например, RequestMappingHandlerAdapter для @Controller), который вызывает целевой метод контроллера, разрешая аргументы и выполняя конвертацию данных.

5. Выполнение метода контроллера: Вызывается метод, аннотированный @RequestMapping, @GetMapping и т.д. Spring автоматически связывает (binds) параметры запроса, тело, заголовки, переменные пути с аргументами метода.

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public UserDto getUser(@PathVariable Long id, 
                           @RequestHeader("User-Agent") String userAgent) {
        // Бизнес-логика
        return userService.findById(id);
    }

    @PostMapping("/users")
    public ResponseEntity<UserDto> createUser(@Valid @RequestBody UserCreateRequest request) {
        UserDto created = userService.create(request);
        return ResponseEntity.created(URI.create("/users/" + created.id())).body(created);
    }
}

6. Обработка возвращаемого значения (HandlerMethodReturnValueHandler): Результат работы метода (String, ResponseEntity, DTO) обрабатывается для формирования ModelAndView. Для @RestController@ResponseBody) объект сериализуется в JSON/XML с помощью HttpMessageConverter (например, MappingJackson2HttpMessageConverter).

7. Обработка исключений (HandlerExceptionResolver): Если в процессе возникло исключение, DispatcherServlet делегирует его обработку цепочке HandlerExceptionResolver. @ControllerAdvice и @ExceptionHandler — стандартный способ глобальной обработки ошибок.

8. Рендеринг ответа (ViewResolver): Для традиционного MVC (с HTML) ViewResolver выбирает шаблон (JSP, Thymeleaf) для рендеринга. Для REST API этот шаг часто пропускается, так как ответ уже сериализован.

9. Пост-обработка в HandlerInterceptor: Вызываются методы postHandle() и afterCompletion() интерцепторов (например, для логирования времени выполнения).

10. Отправка ответа клиенту: DispatcherServlet отправляет окончательный HTTP-ответ через HttpServletResponse.

Визуализация потока:

HTTP Request
    ↓
DispatcherServlet (Front Controller)
    ↓
HandlerMapping (Находит контроллер по URL)
    ↓
HandlerInterceptor.preHandle() (Логирование, безопасность)
    ↓
HandlerAdapter (Вызывает метод контроллера)
    ↓
@Controller Method Execution (Ваша бизнес-логика)
    ↓
HandlerInterceptor.postHandle()
    ↓
Return Value Handling / Message Conversion (Объект → JSON)
    ↓
HandlerExceptionResolver (Если было исключение)
    ↓
ViewResolver (Для MVC с UI)
    ↓
HandlerInterceptor.afterCompletion()
    ↓
HTTP Response (JSON/HTML) → Клиент

Ответ 18+ 🔞

А, слушай, вот эта ваша Spring MVC, она же, блядь, как конвейер на заводе работает, только вместо колбасы — HTTP-запросы перемалывает. Сейчас разложу по полочкам, чтобы даже мартышлюшка поняла.

1. Запрос прилетает в DispatcherServlet: Это типа главный швейцар в ночном клубе, единая дверь для всех. Наследник HttpServlet, короче, царь и бог в одном флаконе. Весь трафик через него.

2. Поиск того, кому передать эстафету (HandlerMapping): Швейцар-то швейцар, но он не сам всех обслуживает. Он смотрит в свой список — кто из HandlerMapping-ов знает, какой контроллер (этот ваш @Controller) за какой URL отвечает. Типа: «А, /users/5? Это к Васе, на второй этаж».

3. Таможенный досмотр (HandlerInterceptor): Прежде чем пустить к Васе, запрос могут остановить охранники-интерцепторы. Вызовут свои методы preHandle(): «А документы есть? А логин-пароль? А не шпион ли?». Могут и не пустить, пидорасы.

4. Найм переводчика и вызов (HandlerAdapter): Допустили до контроллера. Но Вася-контроллер — он же чудак, с ним на одном языке не поговоришь. Нужен адаптер, например, RequestMappingHandlerAdapter. Этот адаптер — как толмач: он берет сырые данные из запроса (параметры, тело, заголовки) и красиво подсовывает их как аргументы в метод Васи. Магия, блядь!

5. Самый сок — выполнение метода контроллера: Вот он, момент истины. Вызывается твой метод, разукрашенный @GetMapping или @PostMapping. Spring уже всё принес, разложил, даже проверил (@Valid), если просили.

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public UserDto getUser(@PathVariable Long id, 
                           @RequestHeader("User-Agent") String userAgent) {
        // Тут твоя бизнес-логика, ради которой всё и затевалось
        return userService.findById(id);
    }

    @PostMapping("/users")
    public ResponseEntity<UserDto> createUser(@Valid @RequestBody UserCreateRequest request) {
        UserDto created = userService.create(request);
        // Возвращаем ответ с кодом 201 Created и ссылкой на нового юзера
        return ResponseEntity.created(URI.create("/users/" + created.id())).body(created);
    }
}

6. Что делать с тем, что вернулось (HandlerMethodReturnValueHandler): Вася что-то вернул — String, ResponseEntity или просто DTO. Надо это как-то клиенту отдать. Для REST (@RestController) объект просто суют в HttpMessageConverter (чаще всего Джексон), который превращает его в JSON. Получилась каша — выноси.

7. Если всё пошло по пизде (HandlerExceptionResolver): А если в процессе Вася накосячил и выбросил исключение? Не беда! DispatcherServlet не паникует, а зовет спасателей — HandlerExceptionResolver. А если у тебя есть @ControllerAdvice с @ExceptionHandler, то это вообще шикарно — все ошибки ловятся в одном месте, как мухи в паутину.

8. Для любителей старины — рендеринг вёх (ViewResolver): Если ты делаешь старый-добрый MVC с HTML-страницами, то тут вступает в игру ViewResolver. Он ищет, какую JSP или Thymeleaf-шаблонную хуйню подставить. В REST этот шаг обычно проскакивают, ибо ответ уже готов в виде JSON.

9. Пост-обработка, или «А помойте за собой» (HandlerInterceptor): Охранники-интерцепторы снова на сцене. Вызываются их методы postHandle() и afterCompletion(). Можно, например, залогировать, сколько времени вся эта канитель длилась. «Запрос обработан за 15 мс, можно было и быстрее, ленивые жопы».

10. Отправка ответа в мир: И наконец, DispatcherServlet берет готовый ответ и пихает его в HttpServletResponse, откуда он утекает к клиенту. Всё, свободен.

Краткая схема, чтобы в голове уложилось:

HTTP-запрос прилетел
    ↓
DispatcherServlet (главный по тарелочкам)
    ↓
HandlerMapping (ищет, кто виноват)
    ↓
HandlerInterceptor.preHandle() (проверяют, не мудак ли)
    ↓
HandlerAdapter (переводчик для контроллера)
    ↓
Метод @Controller (тут твоя магия)
    ↓
HandlerInterceptor.postHandle() (охранники отмечаются)
    ↓
Обработка возвращаемого значения (объект → JSON)
    ↓
HandlerExceptionResolver (если всё ебнулось)
    ↓
ViewResolver (если нужна HTML-страница)
    ↓
HandlerInterceptor.afterCompletion() (уборка после вечеринки)
    ↓
HTTP-ответ улетает к клиенту

Вот и весь сказ, ёпта. Сложно только звучит, а внутри — просто хорошо организованная последовательность действий, как на конвейере. Главное — не забывать, где твой кусок кода в этой цепочке вставляется.