1. DispatcherServlet 구조

Spring MVC의 핵심은 DispatcherServlet이다. 모든 HTTP 요청을 받아 적절한 핸들러에 위임하는 Front Controller 패턴을 구현한다.

[Front Controller 패턴]

클라이언트
    |
    | HTTP 요청
    v
+------------------+
| DispatcherServlet|  ← 모든 요청의 단일 진입점
+------------------+
    |
    | 위임
    v
+------------------+    +------------------+    +------------------+
| OrderController  |    | UserController   |    | ProductController|
+------------------+    +------------------+    +------------------+

DispatcherServlet 초기화

Spring Boot 시작
    |
    v
ServletWebServerApplicationContext 생성
    |
    v
DispatcherServlet 등록 (자동)
    |
    v
DispatcherServlet.init()
    |
    v
WebApplicationContext 연결
    |
    v
전략 컴포넌트 초기화:
  - HandlerMapping 목록
  - HandlerAdapter 목록
  - ViewResolver 목록
  - HandlerExceptionResolver 목록
  - ...

2. 요청 처리 흐름

HTTP 요청 (GET /orders/1)
        |
        v
[DispatcherServlet]
        |
        | 1. getHandler()
        v
[HandlerMapping]
  - RequestMappingHandlerMapping: @RequestMapping 기반
  - BeanNameUrlHandlerMapping: Bean 이름 기반
        |
        | HandlerExecutionChain 반환
        | (Handler + Interceptor 목록)
        v
[DispatcherServlet]
        |
        | 2. getHandlerAdapter()
        v
[HandlerAdapter]
  - RequestMappingHandlerAdapter: @Controller 처리
  - HttpRequestHandlerAdapter: HttpRequestHandler 처리
        |
        | 3. Interceptor.preHandle()
        v
[Interceptor Chain]
        |
        | 4. handle() - 실제 컨트롤러 실행
        v
[Controller Method]
  - ArgumentResolver로 파라미터 바인딩
  - 비즈니스 로직 실행
  - ReturnValueHandler로 반환값 처리
        |
        | ModelAndView 반환
        v
[DispatcherServlet]
        |
        | 5. Interceptor.postHandle()
        v
[Interceptor Chain]
        |
        | 6. processDispatchResult()
        v
[ViewResolver]
  - 뷰 이름 → View 객체 변환
  - @ResponseBody면 MessageConverter 사용
        |
        | 7. View.render()
        v
[View]
  - 템플릿 렌더링 (Thymeleaf, JSP 등)
        |
        | 8. Interceptor.afterCompletion()
        v
HTTP 응답

HandlerMapping

요청 URL을 어떤 핸들러(컨트롤러 메서드)가 처리할지 결정한다.

// RequestMappingHandlerMapping이 처리하는 매핑
@RestController
@RequestMapping("/orders")
public class OrderController {

    @GetMapping("/{id}")         // GET /orders/{id}
    @PostMapping                 // POST /orders
    @PutMapping("/{id}")         // PUT /orders/{id}
    @DeleteMapping("/{id}")      // DELETE /orders/{id}
    @PatchMapping("/{id}")       // PATCH /orders/{id}

    // 조건부 매핑
    @GetMapping(value = "/search",
                params = "type=recent",        // 쿼리 파라미터 조건
                headers = "X-API-Version=2",   // 헤더 조건
                consumes = "application/json", // Content-Type 조건
                produces = "application/json") // Accept 조건
    public List<Order> searchOrders() { ... }
}

HandlerAdapter

다양한 형태의 핸들러(컨트롤러)를 일관된 방식으로 실행할 수 있도록 어댑터 패턴을 적용한다.

// RequestMappingHandlerAdapter가 처리하는 흐름
public class RequestMappingHandlerAdapter {

    public ModelAndView handle(HttpServletRequest request,
                               HttpServletResponse response,
                               Object handler) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        // 1. ArgumentResolver로 파라미터 준비
        Object[] args = resolveArguments(handlerMethod, request);

        // 2. 메서드 실행
        Object returnValue = handlerMethod.invoke(args);

        // 3. ReturnValueHandler로 반환값 처리
        handleReturnValue(returnValue, handlerMethod, response);
    }
}

3. @Controller vs @RestController

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody  // 이것이 유일한 차이
public @interface RestController { }

@RestController = @Controller + @ResponseBody

@Controller (View 반환)

@Controller
public class PageController {

    @GetMapping("/orders")
    public String orders(Model model) {
        model.addAttribute("orders", orderService.findAll());
        return "orders/list";  // ViewResolver → templates/orders/list.html
    }

    @GetMapping("/orders/{id}")
    public String orderDetail(@PathVariable Long id, Model model) {
        model.addAttribute("order", orderService.findById(id));
        return "orders/detail";
    }
}

@RestController (데이터 반환)

@RestController
@RequestMapping("/api/orders")
public class OrderApiController {

    @GetMapping("/{id}")
    public OrderResponse getOrder(@PathVariable Long id) {
        return orderService.findById(id);  // JSON으로 직렬화
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public OrderResponse createOrder(@RequestBody @Valid OrderRequest request) {
        return orderService.create(request);
    }
}

@ResponseBody 동작

컨트롤러 반환값 (OrderResponse 객체)
        |
        v
[HttpMessageConverter]
  - MappingJackson2HttpMessageConverter: Java 객체 → JSON
  - StringHttpMessageConverter: String → text/plain
  - ByteArrayHttpMessageConverter: byte[] → application/octet-stream
        |
        v
HTTP Response Body (JSON 문자열)

Accept 헤더와 Content-Type을 보고 적절한 MessageConverter를 선택한다.


4. ArgumentResolver와 ReturnValueHandler

ArgumentResolver (HandlerMethodArgumentResolver)

컨트롤러 메서드의 파라미터를 어떻게 만들지 결정한다.

// Spring이 기본 제공하는 ArgumentResolver가 처리하는 파라미터들
@GetMapping("/orders")
public String getOrders(
    @PathVariable Long id,             // PathVariableMethodArgumentResolver
    @RequestParam String status,       // RequestParamMethodArgumentResolver
    @RequestBody OrderRequest request, // RequestResponseBodyMethodProcessor
    @ModelAttribute OrderSearch search,// ModelAttributeMethodProcessor
    HttpServletRequest request,        // ServletRequestMethodArgumentResolver
    @RequestHeader String auth,        // RequestHeaderMethodArgumentResolver
    @CookieValue String token,         // ServletCookieValueMethodArgumentResolver
    @SessionAttribute User user,       // SessionAttributeMethodArgumentResolver
    Principal principal,               // PrincipalMethodArgumentResolver
    Locale locale,                     // LocaleContextMethodArgumentResolver
    @AuthenticationPrincipal UserDetails userDetails  // Spring Security
) { ... }

커스텀 ArgumentResolver

// 커스텀 어노테이션
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser { }

// 커스텀 ArgumentResolver 구현
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // @LoginUser 어노테이션이 붙은 파라미터 처리
        return parameter.hasParameterAnnotation(LoginUser.class)
            && parameter.getParameterType().equals(User.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) {
        HttpSession session = ((HttpServletRequest) webRequest.getNativeRequest()).getSession();
        return session.getAttribute("loginUser");
    }
}

// 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginUserArgumentResolver loginUserArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(loginUserArgumentResolver);
    }
}

// 사용
@GetMapping("/mypage")
public String myPage(@LoginUser User loginUser, Model model) {
    model.addAttribute("user", loginUser);
    return "mypage";
}

ReturnValueHandler

컨트롤러의 반환값을 어떻게 처리할지 결정한다.

// 반환 타입별 처리
@GetMapping("/")
public String viewName() { ... }         // ViewNameMethodReturnValueHandler

@GetMapping("/")
public ModelAndView mav() { ... }        // ModelAndViewMethodReturnValueHandler

@GetMapping("/")
@ResponseBody
public OrderDto json() { ... }           // RequestResponseBodyMethodProcessor

@GetMapping("/")
public ResponseEntity<OrderDto> re() { ... }  // HttpEntityMethodProcessor

@GetMapping("/")
public CompletableFuture<OrderDto> async() { ... }  // AsyncTaskMethodReturnValueHandler

5. 인터셉터 vs 필터

필터 (Filter)

Servlet 스펙의 구성요소. Spring 컨텍스트 외부에서 동작.

HTTP 요청
    |
    v
[Filter Chain]          ← Servlet 컨테이너 레벨
  - CharacterEncodingFilter
  - CorsFilter
  - SecurityFilterChain (Spring Security)
    |
    v
[DispatcherServlet]
    |
    v
[Interceptor Chain]     ← Spring MVC 레벨
    |
    v
[Controller]
@Component
public class LoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        log.info("Filter: {} {}", httpRequest.getMethod(), httpRequest.getRequestURI());

        chain.doFilter(request, response);  // 다음 필터 또는 서블릿으로

        log.info("Filter: 응답 완료");
    }
}

인터셉터 (HandlerInterceptor)

Spring MVC 구성요소. Spring 컨텍스트 내부에서 동작. Spring Bean 주입 가능.

@Component
public class AuthInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtTokenProvider jwtTokenProvider;  // Spring Bean 주입 가능

    // 컨트롤러 실행 전
    @Override
    public boolean preHandle(HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (!jwtTokenProvider.validate(token)) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return false;  // false 반환 시 컨트롤러 실행 중단
        }
        return true;
    }

    // 컨트롤러 실행 후, View 렌더링 전
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) {
        // ModelAndView 수정 가능
    }

    // View 렌더링 후 (예외 발생 시에도 실행)
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) {
        // 리소스 정리
    }
}

// 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**")       // 적용할 URL 패턴
                .excludePathPatterns("/api/login", "/api/signup")  // 제외할 패턴
                .order(1);                        // 순서
    }
}

필터 vs 인터셉터 비교

구분 Filter Interceptor
레벨 Servlet 컨테이너 Spring MVC
Spring Bean 주입 불가 (DelegatingFilterProxy 사용 시 가능) 가능
적용 범위 모든 요청 (정적 리소스 포함) DispatcherServlet 이후
예외 처리 @ExceptionHandler 불가 @ExceptionHandler 가능
용도 인코딩, CORS, Security, 로깅 인증/인가, 로깅, API 버전

6. 예외 처리

@ExceptionHandler

특정 컨트롤러에서 발생한 예외를 처리한다.

@RestController
public class OrderController {

    @GetMapping("/{id}")
    public OrderResponse getOrder(@PathVariable Long id) {
        return orderService.findById(id); // OrderNotFoundException 발생 가능
    }

    // 이 컨트롤러에서 발생한 OrderNotFoundException만 처리
    @ExceptionHandler(OrderNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleOrderNotFound(OrderNotFoundException e) {
        return new ErrorResponse("ORDER_NOT_FOUND", e.getMessage());
    }
}

@ControllerAdvice / @RestControllerAdvice

전역 예외 처리. 모든 컨트롤러에서 발생한 예외를 처리한다.

@RestControllerAdvice  // @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {

    // 유효성 검증 실패
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleValidation(MethodArgumentNotValidException e) {
        List<String> errors = e.getBindingResult()
                               .getFieldErrors()
                               .stream()
                               .map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
                               .collect(Collectors.toList());
        return new ErrorResponse("VALIDATION_FAILED", errors.toString());
    }

    // 비즈니스 예외
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusiness(BusinessException e) {
        return ResponseEntity
                .status(e.getHttpStatus())
                .body(new ErrorResponse(e.getCode(), e.getMessage()));
    }

    // 그 외 모든 예외
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse handleException(Exception e) {
        log.error("예상치 못한 예외", e);
        return new ErrorResponse("INTERNAL_SERVER_ERROR", "서버 오류가 발생했습니다.");
    }
}

HandlerExceptionResolver 처리 순서

예외 발생
    |
    v
[ExceptionHandlerExceptionResolver]
  - @ExceptionHandler 탐색 (컨트롤러 → @ControllerAdvice 순)
  - 처리 성공 시 종료
    |
    v (처리 못한 경우)
[ResponseStatusExceptionResolver]
  - @ResponseStatus 어노테이션 탐색
  - ResponseStatusException 처리
    |
    v (처리 못한 경우)
[DefaultHandlerExceptionResolver]
  - Spring MVC 표준 예외 처리
  - TypeMismatchException → 400
  - NoSuchRequestHandlingMethodException → 404
  - HttpRequestMethodNotSupportedException → 405
    |
    v (처리 못한 경우)
Servlet Container로 예외 전달

7. 데이터 바인딩과 유효성 검증

@PostMapping("/orders")
public ResponseEntity<OrderResponse> createOrder(
    @RequestBody @Valid OrderRequest request,  // @Valid: Bean Validation 실행
    BindingResult bindingResult               // 검증 결과 (optional)
) {
    if (bindingResult.hasErrors()) {
        // 직접 처리
    }
    return ResponseEntity.ok(orderService.create(request));
}

// DTO 유효성 규칙
public class OrderRequest {
    @NotNull(message = "상품 ID는 필수입니다")
    private Long productId;

    @Min(value = 1, message = "수량은 1 이상이어야 합니다")
    @Max(value = 100, message = "수량은 100 이하여야 합니다")
    private int quantity;

    @NotBlank(message = "배송 주소는 필수입니다")
    @Size(max = 200, message = "주소는 200자 이하여야 합니다")
    private String address;

    @Email(message = "이메일 형식이 올바르지 않습니다")
    private String email;
}

정리

구성요소 역할
DispatcherServlet Front Controller, 모든 요청의 진입점
HandlerMapping URL → Handler 매핑 결정
HandlerAdapter Handler를 일관된 방식으로 실행
ArgumentResolver 컨트롤러 파라미터 바인딩
ReturnValueHandler 컨트롤러 반환값 처리
MessageConverter Java 객체 ↔ JSON/XML 변환
ViewResolver 뷰 이름 → View 객체 변환
Filter Servlet 레벨. 모든 요청에 적용
Interceptor Spring MVC 레벨. Bean 주입 가능
@ExceptionHandler 컨트롤러 단위 예외 처리
@ControllerAdvice 전역 예외 처리

카테고리:

업데이트: