모든 요청이 거쳐 가는 단일 진입점(프론트 컨트롤러). Spring MVC의 뼈대다.
요청 하나가 지나는 사슬
요청
↓
DispatcherServlet
↓ HandlerMapping 어느 컨트롤러 메서드가 이 URL 을 맡나
↓ HandlerAdapter 그 핸들러를 어떻게 호출할까
↓ ArgumentResolver 메서드 파라미터를 채운다 (@RequestBody · @PathVariable ...)
↓ [ 컨트롤러 메서드 ]
↓ ReturnValueHandler 반환값을 어떻게 처리할까
↓ HttpMessageConverter 객체 → JSON 직렬화
↓
응답
입력과 출력 단계가 모두 확장 지점이라는 게 핵심이다 — 커스텀 애노테이션으로 파라미터를 채우거나 응답을 감싸는 일이 여기서 이뤄진다.
// 커스텀 ArgumentResolver — @LoginUser 로 인증 정보를 바로 주입받는다
@Component
class LoginUserResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter p) {
return p.hasParameterAnnotation(LoginUser.class);
}
public Object resolveArgument(...) { return currentUser(); }
}
필터 vs 인터셉터 — 위치가 다르다
요청 → [서블릿 필터] → DispatcherServlet → [인터셉터] → 컨트롤러
- (서블릿 컨테이너) — (Spring MVC)
| 필터 | 인터셉터 | |
|---|---|---|
| 소속 | 서블릿 컨테이너 | Spring MVC |
| 볼 수 있는 것 | ServletRequest | 어떤 핸들러가 선택됐는지(HandlerMethod) |
| 스프링 빈 주입 | 제한적 | 자유롭다 |
| 쓰임 | 인코딩·CORS·보안 필터 체인 | 인증 확인·로깅·핸들러별 처리 |
"어느 컨트롤러가 처리할지"를 알아야 하면 인터셉터여야 한다. 필터 시점에는 아직 매핑이 안 됐다.
예외를 한 곳에서 표준 형식으로
@RestControllerAdvice
class ApiExceptionHandler {
@ExceptionHandler(OrderNotFoundException.class)
ProblemDetail handle(OrderNotFoundException e) {
var pd = ProblemDetail.forStatusAndDetail(NOT_FOUND, e.getMessage());
pd.setType(URI.create("https://api.example.com/errors/order-not-found"));
return pd; // RFC 9457 application/problem+json
}
}
검증은 경계에서
@PostMapping("/orders")
OrderResponse create(@Valid @RequestBody OrderRequest req) { ... }
// 실패하면 MethodArgumentNotValidException → @RestControllerAdvice 에서 400 으로
면접 함정
- ❌ "필터와 인터셉터는 같은 것" → 소속과 시점이 다르다. 필터는 핸들러를 모른다.
- ❌ "@RestControllerAdvice가 모든 예외를 잡는다" → 필터에서 난 예외는 못 잡는다. DispatcherServlet 이전이기 때문이다.