Урок по треку Spring и Spring Boot 7 мин

Spring MVC и REST

DispatcherServlet, контроллеры, валидация, обработка ошибок, фильтры и интерсепторы.

Почти каждый бэкенд на Spring отдаёт REST API. На собесе просят описать путь запроса, разницу аннотаций, валидацию и единый формат ошибок, а на senior добавляют фильтры против интерсепторов и потоки.

Путь запроса

  1. Tomcat принимает соединение, запрос получает поток из пула (200 потоков по умолчанию, server.tomcat.threads.max).
  2. Цепочка фильтров сервлета (в том числе Spring Security).
  3. DispatcherServlet: единая точка входа Spring MVC.
  4. HandlerMapping находит метод контроллера по URL и методу HTTP.
  5. Интерсепторы HandlerInterceptor.preHandle.
  6. HandlerAdapter вызывает метод: аргументы собирают HandlerMethodArgumentResolver-ы (@PathVariable, @RequestBody через HttpMessageConverter), валидация.
  7. Результат сериализуется конвертером (Jackson для JSON).
  8. Исключения обрабатывает HandlerExceptionResolver (там же работает @ExceptionHandler).

Фильтр работает на уровне сервлета и видит любой запрос, даже не дошедший до Spring MVC. Интерсептор знает, какой метод контроллера будет вызван. Аутентификацию делают фильтрами, логику вокруг конкретных обработчиков интерсепторами или аспектами.

Контроллер

@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
    private final OrderService service;

    @GetMapping("/{id}")
    public OrderDto get(@PathVariable long id) {
        return service.get(id);
    }

    @GetMapping
    public Page<OrderDto> list(@RequestParam(defaultValue = "0") int page,
                               @RequestParam(defaultValue = "20") int size) { ... }

    @PostMapping
    public ResponseEntity<OrderDto> create(@Valid @RequestBody CreateOrderRequest req) {
        OrderDto dto = service.create(req);
        return ResponseEntity.created(URI.create("/api/orders/" + dto.id())).body(dto);
    }
}
  • @RestController = @Controller + @ResponseBody на всех методах.
  • @PathVariable часть пути, @RequestParam параметр строки запроса, @RequestBody тело (JSON), @RequestHeader заголовок.
  • ResponseEntity задаёт статус и заголовки явно; @ResponseStatus статус по умолчанию.

Валидация

На DTO ставят ограничения Jakarta Bean Validation (@NotBlank, @Email, @Positive, @Size), а на параметр @Valid. Ошибка тела запроса даёт MethodArgumentNotValidException и 400. Для проверки параметров методов сервиса нужен @Validated на классе.

Сущности JPA наружу не отдают: DTO отделяют API от схемы БД, не тянут ленивые связи в сериализацию (LazyInitializationException, бесконечная рекурсия двунаправленных связей) и не дают клиенту переписать поля, которые он не должен менять (mass assignment).

Обработка ошибок

@RestControllerAdvice
public class ApiErrors {
    @ExceptionHandler(OrderNotFoundException.class)
    ProblemDetail notFound(OrderNotFoundException e) {
        return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
    }
}

ProblemDetail (Spring 6) реализует стандарт RFC 9457 (ранее RFC 7807) application/problem+json. В Boot его включает spring.mvc.problemdetails.enabled=true. Стектрейсы клиенту не отдают.

Потоки и блокировки

Spring MVC работает по модели «поток на запрос». Долгий внешний вызов держит поток Tomcat, и при 200 одновременных медленных запросах сервер перестаёт отвечать. Варианты: таймауты на клиентах, отдельные пулы, виртуальные потоки (spring.threads.virtual.enabled=true, Boot 3.2+) или реактивный стек WebFlux.

Для HTTP-вызовов к другим сервисам в Spring 6.1+ есть RestClient (синхронный, современная замена RestTemplate) и WebClient (реактивный).

Частые ошибки

  • Отдавать JPA-сущности из контроллера.
  • Забыть @Valid, и аннотации на DTO молча не проверяются.
  • Ловить исключения в каждом контроллере вместо @RestControllerAdvice.
  • Возвращать 200 с {"error": ...} вместо правильного статуса.

Что спросят на собесе

  • Что происходит с запросом от сокета до метода контроллера?
  • Чем @Controller отличается от @RestController, @PathVariable от @RequestParam?
  • Фильтр или интерсептор: в чём разница и что когда?
  • Как сделать валидацию и единый формат ошибок?
  • Почему не стоит возвращать сущности из контроллера?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.