이번 글에서는 Spring MVC의 핵심 구조인 DispatcherServlet을 중심으로
HTTP 요청이 어떻게 필터/인터셉터/컨트롤러/뷰까지 전달되는지,
그리고 에러 발생 시 어떻게 처리되고, 예외 핸들러가 작동하는지를 자세히 정리한다.
실무에서 발생하는 복잡한 요청 흐름을 이해하고, 디버깅과 커스터마이징 능력을 키우는 데 목적이 있다.
✅ DispatcherServlet이란?
- 스프링 MVC의 중심이자 모든 요청의 진입점
- Front Controller 패턴을 따르며, 전체 요청을 받아 적절한 컨트롤러에게 위임
- 보통
web.xml 또는 @SpringBootApplication 기반 설정에서 자동 등록됨
🔁 요청 흐름 요약 (Spring MVC)
[Client]
↓
[FilterChain (Servlet Filter)]
↓
[DispatcherServlet]
├── HandlerMapping → 어떤 컨트롤러?
├── HandlerAdapter → 어떻게 실행할까?
├── Controller → 실제 로직 처리
├── ViewResolver → 어떤 뷰로 응답할까?
↓
[HttpServletResponse]
🔍 주요 컴포넌트 상세
1. HandlerMapping
- URL, 메서드에 따라 어떤 컨트롤러 메서드를 실행할지 결정
- 기본 구현:
RequestMappingHandlerMapping
2. HandlerAdapter
- 매핑된 컨트롤러를 실제로 실행
- 컨트롤러의 타입에 따라 적절한 어댑터 사용 (Annotation, Simple, Servlet 등)
3. ViewResolver
- 컨트롤러 반환값(View 이름 등)을 통해 어떤 응답 뷰를 렌더링할지 결정
- JSON 응답 시
MappingJackson2JsonView, 템플릿 엔진 시 ThymeleafView 등
⚙️ DispatcherServlet과 Filter / Interceptor 순서
| 순서 |
컴포넌트 |
설명 |
| 1 |
Filter |
서블릿 단위에서 동작. 인증/로깅, CORS 처리 등에 활용 |
| 2 |
Interceptor |
스프링 내부. Handler 실행 전/후 로직 정의 가능 |
| 3 |
Controller |
실제 비즈니스 로직 실행 |
🧯 예외 처리 흐름
1. RuntimeException (Unchecked)
- 개발자가 명시적으로 잡지 않아도 전달됨
- Spring은 내부적으로
ExceptionHandlerExceptionResolver를 통해
@ExceptionHandler, @ControllerAdvice를 탐색하여 처리
2. CheckedException
- 반드시
try-catch 혹은 throws 처리 필요
- Service/Repository 계층에서 던져도 Controller까지 위임 가능
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<?> handleNotFound(EntityNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
}
📌 실무 팁
- 비즈니스 예외는
RuntimeException을 상속한 CustomException으로 처리하면 유연성 증가
- 공통 예외 포맷 (
ErrorResponse, code, message)을 정의해 일관된 응답 제공 가능
🧱 실무에서 DispatcherServlet이 중요한 이유
| 항목 |
이유 |
| 디버깅 |
요청이 왜 막혔는지, 왜 응답이 안 오는지 확인 시 DispatcherServlet 경로 필수 |
| 커스터마이징 |
커스텀 인터셉터, 글로벌 예외 처리, 뷰 변환기 등 설정 가능 |
| 로깅 & 추적 |
요청 흐름 전체를 하나의 체인으로 관리 가능 (ex: Mapped Diagnostic Context) |
🛠 커스터마이징 예시
✅ 커스텀 인터셉터 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**");
}
}
✅ 전역 예외 처리 설정
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBiz(BusinessException e) {
return ResponseEntity.status(e.getCode()).body(new ErrorResponse(...));
}
}
💡 학습 정리
- DispatcherServlet은 요청의 흐름을 결정하는 Spring의 핵심 구성요소이다.
- 요청 → 필터 → 인터셉터 → 핸들러 → 뷰 렌더링까지 이어지는 구조는 디버깅과 보안, 성능 최적화에 중요하다.
- Unchecked와 Checked 예외는 처리 방식이 다르며, 공통 예외 설계를 통해 안정성을 높일 수 있다.
- 내부 동작을 이해하면 커스터마이징이 자유로워지고, 예측 가능한 구조 설계가 가능해진다.