Ответ
Обработка запроса в 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-ответ улетает к клиенту
Вот и весь сказ, ёпта. Сложно только звучит, а внутри — просто хорошо организованная последовательность действий, как на конвейере. Главное — не забывать, где твой кусок кода в этой цепочке вставляется.