Programming/Spring

스프링 MVC와 DispatcherServlet 내부 동작: 모든 요청은 여기로 집결!

추천캐릭터 2025. 8. 24. 08:34
728x90
반응형

요약 한 줄
스프링 MVC는 DispatcherServlet이 중앙에서 요청을 받아 HandlerMapping → HandlerAdapter → (Controller) → ViewResolver → View 순으로 흐름을 조율하고, 중간에 필터/인터셉터/예외처리/리졸버들이 참여하는 파이프라인입니다.

목차

  1. 전체 흐름(빅픽처)
  2. 주요 컴포넌트 한 줄 정의
  3. 요청~응답 단계별 상세
  4. 인터셉터 vs 필터 vs AOP
  5. 예외 처리 흐름(@ExceptionHandler, @ControllerAdvice, HandlerExceptionResolver)
  6. 확장 포인트(커스터마이징 체크리스트)
  7. 실전 코드 스니펫 모음
  8. 면접/시험 포인트 & 체크리스트

1) 전체 흐름(빅픽처)

HTTP 요청
  ↓ (Servlet Filter 체인)
DispatcherServlet
  ↓ HandlerMapping(매핑 찾기)
HandlerExecutionChain(핸들러 + 인터셉터들)
  ↓ HandlerAdapter(호출 어댑터)
@Controller/@RestController 메서드 실행
  ↓ (Model/ModelAndView or @ResponseBody)
ViewResolver(뷰 선택) ─ 또는 HttpMessageConverter(바디 변환)
  ↓ View 렌더링(템플릿/정적리소스) or JSON 직렬화
HTTP 응답

  

2) 주요 컴포넌트 한 줄 정의

  • DispatcherServlet: 프론트 컨트롤러. 요청/응답의 트래픽 경찰.
  • HandlerMapping: 어떤 컨트롤러 메서드가 이 URL/HTTP 메서드를 처리하는지 탐색.
  • HandlerAdapter: 찾아낸 핸들러를 실행 가능한 형태로 호출.
  • HandlerMethodArgumentResolver: 컨트롤러 파라미터(@RequestParam, @PathVariable, @RequestBody, 커스텀 타입 등) 주입.
  • HttpMessageConverter: HTTP 바디 ↔ 객체(JSON, XML 등) 직렬화/역직렬화.
  • ViewResolver: 뷰 이름을 템플릿(View)으로 해석(Thymeleaf, JSP 등).
  • HandlerExceptionResolver: 예외를 적절한 응답/뷰로 변환.
  • HandlerInterceptor: 컨트롤러 전/후/완료 시점 가로채기(로깅, 인증 등).

3) 요청~응답 단계별 상세

  1. 필터 체인
    • 서블릿 컨테이너 레벨. 인코딩 처리, 보안(스프링 시큐리티), 공통 로깅 등.
  2. DispatcherServlet 진입
    • doDispatch()에서 실질 처리 시작.
    • HandlerMapping으로 HandlerExecutionChain(핸들러 + 인터셉터 목록) 획득.
  3. 인터셉터(preHandle)
    • 인증/인가, 트랜잭션 컨텍스트, 트레이싱 ID 부여 등.
  4. HandlerAdapter → 컨트롤러 실행
    • 메서드 파라미터는 ArgumentResolver들이 채워줌.
    • 바디(JSON 등)는 HttpMessageConverter로 역직렬화.
  5. 핸들러 반환값 처리
    • 템플릿 렌더: ModelAndView → ViewResolver로 뷰 탐색 후 렌더.
    • REST 응답: @ResponseBody/ResponseEntity → HttpMessageConverter로 JSON 직렬화.
  6. 인터셉터(postHandle, afterCompletion)
    • 모델 가공, 뷰 렌더 이후 리소스 정리, 예외 로깅 등.
  7. 예외 발생 시
    • @ExceptionHandler(로컬) → @ControllerAdvice(글로벌) → HandlerExceptionResolver 순으로 처리.

4) 인터셉터 vs 필터 vs AOP

구분                                    적용 레벨                          주요 시점                                                       사용 예

Filter 서블릿 컨테이너 DispatcherServlet 전/후 인코딩, 보안, CORS, 공통 로깅
Interceptor 스프링 MVC 컨트롤러 전/후/완료 로그인 체크, 요청 컨텍스트, 성능 측정
AOP 스프링 빈 메서드 메서드 호출 단위 트랜잭션, 캐시, 로깅 단면화
 

인증/인가는 필터/시큐리티, 컨트롤러 전후 로직은 인터셉터, 서비스/리포지토리横단 관심사는 AOP가 깔끔합니다.

5) 예외 처리 흐름

우선순위: 로컬 @ExceptionHandler → 글로벌 @ControllerAdvice → HandlerExceptionResolver

  • REST라면 적절한 상태코드 + JSON 바디로 통일.
  • HTML 뷰 프로젝트면 공통 오류 페이지로 렌더.

6) 확장 포인트 체크리스트

  • WebMvcConfigurer로 인터셉터/리졸버/메시지컨버터 추가
  • HandlerMethodArgumentResolver로 커스텀 파라미터 주입(예: @CurrentUser)
  • MessageConverter로 Protobuf/CSV 등 포맷 지원
  • LocaleResolver/TimeZone로 지역화
  • ContentNegotiation으로 ?format=json/Accept 헤더 대응
  • ViewResolver 우선순위 조정(Thymeleaf, JSP, JSON 등)
  • ExceptionResolver 또는 @ControllerAdvice로 에러 표준화
  • 정적 리소스 캐싱과 ResourceHandler 튜닝

7) 실전 코드 스니펫

7-1. 간단 컨트롤러

@RestController
@RequestMapping("/api")
public class HelloController {
  @GetMapping("/hello")
  public Map<String, Object> hello(@RequestParam String name) {
    return Map.of("message", "Hello " + name);
  }
}

7-2. 인터셉터 등록

@Component
public class LogInterceptor implements HandlerInterceptor {
  @Override
  public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
    req.setAttribute("startAt", System.currentTimeMillis());
    return true;
  }
  @Override
  public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) {
    long took = System.currentTimeMillis() - (long) req.getAttribute("startAt");
    System.out.println("[TIME] " + req.getRequestURI() + " -> " + took + "ms");
  }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LogInterceptor()).addPathPatterns("/api/**");
  }
}

7-3. 커스텀 ArgumentResolver (예: @CurrentUser)

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {}

@Component
public class CurrentUserResolver implements HandlerMethodArgumentResolver {
  @Override public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(CurrentUser.class);
  }
  @Override public Object resolveArgument(MethodParameter p, ModelAndViewContainer mav,
      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
    return webRequest.getNativeRequest(HttpServletRequest.class).getAttribute("user");
  }
}

@Configuration
class MvcConfig implements WebMvcConfigurer {
  @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    resolvers.add(new CurrentUserResolver());
  }
}

7-4. 글로벌 예외 처리

@ControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(IllegalArgumentException.class)
  public ResponseEntity<Map<String, Object>> handleBadRequest(IllegalArgumentException e) {
    return ResponseEntity.badRequest().body(Map.of(
      "error", "BAD_REQUEST",
      "message", e.getMessage()
    ));
  }
}

7-5. 메시지 컨버터 추가(예: CSV)

@Configuration
public class HttpMsgConfig implements WebMvcConfigurer {
  @Override
  public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(0, new CsvHttpMessageConverter()); // 우선 등록
  }
}

8) 면접/시험 포인트 & 체크리스트

  • DispatcherServlet이 하는 일 3줄로 설명할 수 있는가?
  • HandlerMapping/Adapter/ExceptionResolver/MessageConverter 역할 구분 가능한가?
  • 인터셉터 vs 필터 vs AOP 적용 위치를 사례로 설명할 수 있는가?
  • REST에서 Content NegotiationHttpMessageConverter의 연결을 아는가?
  • 예외를 일관된 응답 스키마로 표준화하는가?
728x90
반응형