Почти каждый бэкенд на Spring отдаёт REST API. На собесе просят описать путь запроса, разницу аннотаций, валидацию и единый формат ошибок, а на senior добавляют фильтры против интерсепторов и потоки.
Путь запроса
- Tomcat принимает соединение, запрос получает поток из пула (200 потоков по умолчанию,
server.tomcat.threads.max). - Цепочка фильтров сервлета (в том числе Spring Security).
- DispatcherServlet: единая точка входа Spring MVC.
HandlerMappingнаходит метод контроллера по URL и методу HTTP.- Интерсепторы
HandlerInterceptor.preHandle. HandlerAdapterвызывает метод: аргументы собираютHandlerMethodArgumentResolver-ы (@PathVariable,@RequestBodyчерезHttpMessageConverter), валидация.- Результат сериализуется конвертером (Jackson для JSON).
- Исключения обрабатывает
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? - Фильтр или интерсептор: в чём разница и что когда?
- Как сделать валидацию и единый формат ошибок?
- Почему не стоит возвращать сущности из контроллера?