요약 한 줄
스프링 MVC는 DispatcherServlet이 중앙에서 요청을 받아 HandlerMapping → HandlerAdapter → (Controller) → ViewResolver → View 순으로 흐름을 조율하고, 중간에 필터/인터셉터/예외처리/리졸버들이 참여하는 파이프라인입니다.
목차
- 전체 흐름(빅픽처)
- 주요 컴포넌트 한 줄 정의
- 요청~응답 단계별 상세
- 인터셉터 vs 필터 vs AOP
- 예외 처리 흐름(@ExceptionHandler, @ControllerAdvice, HandlerExceptionResolver)
- 확장 포인트(커스터마이징 체크리스트)
- 실전 코드 스니펫 모음
- 면접/시험 포인트 & 체크리스트
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) 요청~응답 단계별 상세
- 필터 체인
- 서블릿 컨테이너 레벨. 인코딩 처리, 보안(스프링 시큐리티), 공통 로깅 등.
- DispatcherServlet 진입
- doDispatch()에서 실질 처리 시작.
- HandlerMapping으로 HandlerExecutionChain(핸들러 + 인터셉터 목록) 획득.
- 인터셉터(preHandle)
- 인증/인가, 트랜잭션 컨텍스트, 트레이싱 ID 부여 등.
- HandlerAdapter → 컨트롤러 실행
- 메서드 파라미터는 ArgumentResolver들이 채워줌.
- 바디(JSON 등)는 HttpMessageConverter로 역직렬화.
- 핸들러 반환값 처리
- 템플릿 렌더: ModelAndView → ViewResolver로 뷰 탐색 후 렌더.
- REST 응답: @ResponseBody/ResponseEntity → HttpMessageConverter로 JSON 직렬화.
- 인터셉터(postHandle, afterCompletion)
- 모델 가공, 뷰 렌더 이후 리소스 정리, 예외 로깅 등.
- 예외 발생 시
- @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 Negotiation과 HttpMessageConverter의 연결을 아는가?
- 예외를 일관된 응답 스키마로 표준화하는가?
'Programming > Spring' 카테고리의 다른 글
| 🌱 스프링(Spring)의 탄생과 철학 – Rod Johnson의 한 줄기 ‘봄’ (0) | 2022.11.21 |
|---|---|
| 📌Lombok 어노테이션 총정리: Getter부터 @Data까지 코드가 줄어든다! (0) | 2022.11.02 |
| @Configuration 안에 @Bean을 사용해야 하는 이유, proxyBeanMethods (0) | 2022.10.30 |
| 빈 등록을 위한 어노테이션 (0) | 2022.10.30 |
| @Controller와 @RestController (0) | 2022.10.30 |




