Spring FrameworkMiddleTechnical
Что такое Spring MVC? Объясните жизненный цикл запроса в DispatcherServlet.
Spring MVC — веб-фреймворк на паттерне Front Controller: DispatcherServlet принимает все запросы, делегирует HandlerMapping для поиска контроллера, HandlerAdapter для его вызова, ViewResolver для рендеринга ответа.
Spring MVC и DispatcherServlet
Spring MVC реализует паттерн Front Controller: единственный сервлет DispatcherServlet перехватывает все HTTP-запросы и координирует обработку через цепочку специализированных компонентов.
Жизненный цикл запроса
- Клиент отправляет запрос
GET /api/orders/42 DispatcherServletполучает запрос (зарегистрирован на/*или/)- HandlerMapping определяет обработчик:
RequestMappingHandlerMappingсопоставляет URL с методом контроллера - HandlerInterceptor.preHandle — вызываются все зарегистрированные интерцепторы
- HandlerAdapter вызывает метод контроллера (
RequestMappingHandlerAdapterобрабатывает@RequestMapping) - Контроллер возвращает
ModelAndViewили (при@ResponseBody) записывает ответ напрямую черезHttpMessageConverter - HandlerInterceptor.postHandle — для случаев с ModelAndView
- ViewResolver (если не REST) разрешает логическое имя представления в конкретный View
- View рендерит ответ
- HandlerInterceptor.afterCompletion — вызывается в любом случае (в т.ч. при исключении)
Пример контроллера
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/{id}")
public ResponseEntity<OrderDto> getOrder(@PathVariable Long id) {
return orderService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<OrderDto> createOrder(
@RequestBody @Valid CreateOrderRequest req,
UriComponentsBuilder ucb) {
OrderDto created = orderService.create(req);
URI location = ucb.path("/api/orders/{id}")
.buildAndExpand(created.getId()).toUri();
return ResponseEntity.created(location).body(created);
}
// Глобальная обработка исключений
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<Map<String, String>> handleNotFound(OrderNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", ex.getMessage()));
}
}
Регистрация HandlerInterceptor
@Component
public class RequestLoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
System.out.println("Request: " + req.getMethod() + " " + req.getRequestURI());
return true; // false = запрос прерван
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RequestLoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/health");
}
}
HttpMessageConverter
При @ResponseBody или @RestController DispatcherServlet не использует ViewResolver — вместо этого HandlerAdapter выбирает подходящий HttpMessageConverter (например, MappingJackson2HttpMessageConverter для JSON) на основе заголовков Accept и Content-Type.
Подводные камни
- При наличии двух
DispatcherServlet(основного и дополнительного, например для разных URL) у них разные контексты приложения — бины из одного недоступны в другом. - Порядок
HandlerInterceptorважен: они регистрируются в том порядке, что вызовыpreHandleидут в прямом порядке, аafterCompletion— в обратном. - Если
preHandleвозвращаетfalse, ответ должен быть записан вручную вHttpServletResponse, иначе клиент получит пустой 200. - Content Negotiation: если контроллер возвращает объект, а клиент требует
Accept: application/xml, но конвертер XML не подключён — вернётся 406 Not Acceptable. @ExceptionHandlerв контроллере обрабатывает исключения только этого контроллера; для глобальной обработки нужен@ControllerAdvice.- Метод
afterCompletionинтерцептора вызывается даже при исключении, но само исключение уже обработано — нельзя его перехватить здесь. - При использовании
@EnableWebMvcSpring Boot автоконфигурация MVC отключается; нужно вручную настраиватьMessageConverter,ViewResolverи т.д.
Common mistakes
- Путать термин «spring mvc dispatcherservlet» с соседним механизмом Spring Framework.
- Не называть границу lifecycle, transaction, thread или request для «spring mvc dispatcherservlet».
- Игнорировать production-эффекты «spring mvc dispatcherservlet»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «spring mvc dispatcherservlet» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «spring mvc dispatcherservlet».
- Уточнить, какие настройки или API меняют «spring mvc dispatcherservlet» в реальном сервисе.