Перейти к содержанию

Spring Web MVC

Spring Web MVC - модуль Spring Framework для веб-приложений поверх Jakarta Servlet API. Вся обработка запроса идёт через одну точку входа - DispatcherServlet.

  • spring-web - базовый модуль: HTTP-абстракции, RestClient, RestTemplate, базовые фильтры, multipart-инфраструктура. MVC в нём нет
  • spring-webmvc - полный MVC-стек: DispatcherServlet, контроллеры, view resolvers, message converters. Зависит от spring-web

MVC в Spring:

  • Model - Model или ModelMap: атрибуты, которые передаются в представление
  • View - шаблон Thymeleaf, JSP, FreeMarker для HTML
  • Controller - класс с @Controller или @RestController: принимает запрос, вызывает сервис, отдаёт данные во View
  • REST body - не классический View, а результат HandlerMethodReturnValueHandler + HttpMessageConverter

Порядок страниц раздела - по пути запроса: архитектура (эта страница) → контроллеры → Model, View и REST → ошибки, interceptors и фильтры → файлы, async, сессии → конфигурация, практики и тесты.


Servlet stack

Spring MVC работает внутри servlet-контейнера и управляет обработкой только внутри одного запроса. Всё ниже делает контейнер.

Servlet Container (Tomcat, Jetty, Undertow):

  • принимает TCP/HTTP-соединение и разбирает HTTP
  • создаёт HttpServletRequest и HttpServletResponse
  • выделяет поток из пула (ThreadPoolExecutor в Tomcat)
  • вызывает зарегистрированный Servlet, у нас это DispatcherServlet
  • перед сервлетом прогоняет цепочку servlet-фильтров (см. Filters)

Модель - синхронная и блокирующая:

  • каждый запрос обрабатывает один поток из пула
  • поток занят, пока не сформирован ответ
  • конкурентность даёт размер пула потоков

См. Асинхронная обработка.


Путь запроса

Клиент отправляет HTTP-запрос
  │
  ▼
Servlet Container (Tomcat / Jetty / Undertow)
  │  принимает TCP/HTTP, создаёт HttpServletRequest / HttpServletResponse
  ▼
Servlet Filter chain
  │  CharacterEncodingFilter, Spring Security, CORS, свои фильтры
  ▼
DispatcherServlet
  ├─► HandlerMapping
  │      └─► ищет handler: контроллер + метод
  ├─► HandlerExecutionChain
  │      └─► handler + список HandlerInterceptor
  ├─► HandlerInterceptor.preHandle(...)
  ├─► HandlerAdapter
  │      └─► знает, как вызвать найденный handler
  ├─► HandlerMethodArgumentResolver
  │      └─► @PathVariable, @RequestParam, @RequestBody, Model, HttpServletRequest...
  ├─► Data Binding / Type Conversion / Validation
  ├─► метод контроллера
  │      └─► вызывает сервисный слой и возвращает результат
  ├─► HandlerMethodReturnValueHandler
  │      └─► решает, что делать с результатом
  ├─► либо ViewResolver + View          → HTML
  ├─► либо HttpMessageConverter         → JSON / XML / text в тело ответа
  ├─► HandlerInterceptor.postHandle(...)
  ├─► HandlerExceptionResolver          → если было исключение
  └─► HandlerInterceptor.afterCompletion(...)
  │
  ▼
Клиент получает HTTP-ответ

Итого: контроллер видит только готовые аргументы и возвращает объект. Всё остальное - найти метод, собрать аргументы, превратить результат в HTML или JSON - делает инфраструктура DispatcherServlet.


DispatcherServlet

DispatcherServlet - Front Controller Spring MVC: обычный HttpServlet, зарегистрированный на /, который принимает все запросы и раздаёт их остальным компонентам.

Front Controller - паттерн, при котором один объект принимает все запросы и делегирует их обработчикам. Даёт одно место для сквозных задач (логирование, безопасность, i18n), централизованную маршрутизацию и простое добавление общего поведения.

Servlet -> GenericServlet -> HttpServlet -> HttpServletBean -> FrameworkServlet -> DispatcherServlet

Компоненты, которые он координирует - это Spring-бины в WebApplicationContext:

  • HandlerMapping - может быть несколько
  • HandlerAdapter - может быть несколько
  • ViewResolver - может быть несколько, с приоритетом
  • HandlerExceptionResolver
  • MultipartResolver
  • LocaleResolver

Своего бина нет - берётся дефолтный из DispatcherServlet.properties.

При старте DispatcherServlet получает WebApplicationContext (без Boot создаёт свой, в Boot берёт общий контекст приложения) и находит или создаёт все MVC-компоненты.

На каждый запрос - doDispatch(). Упрощённо:

HandlerExecutionChain chain = getHandler(request);                        // 1. найти handler
HandlerAdapter adapter = getHandlerAdapter(chain.getHandler());           // 2. найти adapter
chain.applyPreHandle(request, response);                                  // 3. preHandle interceptor'ов
ModelAndView mv = adapter.handle(request, response, chain.getHandler());  // 4. вызвать контроллер
chain.applyPostHandle(request, response, mv);                             // 5. postHandle
processDispatchResult(request, response, chain, mv, ex);                  // 6. рендер или обработка ошибки
chain.triggerAfterCompletion(request, response, ex);                      // 7. afterCompletion

В реальном коде вокруг этого обработка multipart, async, исключений и очистка ресурсов. После запроса публикуется событие RequestHandledEvent.


Handler-инфраструктура

HandlerMapping - поиск обработчика: какой контроллер и метод отвечают на этот запрос.

  • смотрит URL-шаблон (/users/{id}), HTTP-метод, заголовки, параметры, content-type
  • возвращает HandlerExecutionChain
  • DispatcherServlet перебирает все HandlerMapping по порядку, побеждает первый, который вернул handler
Реализация Что маппит
RequestMappingHandlerMapping Главная: методы с @RequestMapping, @GetMapping и т.д.
BeanNameUrlHandlerMapping URL на имя бина
RouterFunctionMapping Функциональные роуты (RouterFunction)

RequestMappingHandlerMapping при старте сканирует все @Controller-бины и строит карту {метод, URL, params, headers, consumes, produces} -> HandlerMethod.


HandlerAdapter - знание, как вызвать конкретный тип handler'а.

Реализация Что вызывает
RequestMappingHandlerAdapter Методы контроллеров с @RequestMapping
SimpleControllerHandlerAdapter Старый интерфейс Controller
HandlerFunctionAdapter Функциональные handler'ы

RequestMappingHandlerAdapter держит списки HandlerMethodArgumentResolver и HandlerMethodReturnValueHandler:

  1. разбирает сигнатуру метода контроллера
  2. для каждого параметра вызывает подходящий resolver: @PathVariable из URI, @RequestParam из query, @RequestBody из тела (см. Параметры методов)
  3. вызывает метод через reflection
  4. отдаёт результат подходящему return value handler'у

HandlerExecutionChain - handler + упорядоченный список interceptor'ов.

public class HandlerExecutionChain {
    private final Object handler;                     // метод контроллера
    private List<HandlerInterceptor> interceptorList;
}

Порядок выполнения:

  1. preHandle всех interceptor'ов по порядку. Если один вернул false, цепочка прерывается
  2. сам handler
  3. postHandle в обратном порядке
  4. afterCompletion в обратном порядке, только для interceptor'ов, чей preHandle прошёл

Что делает каждый метод interceptor'а и чем он отличается от фильтра - в Interceptors.


HandlerMethodReturnValueHandler - обработка результата метода контроллера:

  • @ResponseBody или @RestController - HttpMessageConverter сериализует объект в тело ответа
  • String или ModelAndView - ViewResolver ищет шаблон
  • ResponseEntity - выставляет статус, заголовки и тело

См. Model, View и REST.


HandlerExceptionResolver - обработка исключения, которое вылетело в цепочке. DispatcherServlet передаёт его резолверам по приоритету:

  • ExceptionHandlerExceptionResolver - методы @ExceptionHandler
  • ResponseStatusExceptionResolver - исключения с @ResponseStatus
  • DefaultHandlerExceptionResolver - стандартные исключения Spring

См. Обработка исключений.


Формирование ответа

  1. Выставляется HTTP-статус.
  2. Выставляются заголовки: Content-Type, Cache-Control и т.д.
  3. Записывается тело: HTML, JSON, файл.
  4. Servlet-контейнер отправляет ответ клиенту.

Что может вернуть контроллер: отрендеренный HTML через View, JSON через HttpMessageConverter, redirect, файл, ResponseEntity (см. Возвращаемые значения).