JAVA. DispatcherServlet
Spring MVC의 핵심인 DispatcherServlet에 대해 알아보자.
1. DispatcherServlet 이란?
DispatcherServlet은 Spring MVC의 핵심 컴포넌트로, Front Controller 패턴을 구현한 서블릿이다. 클라이언트의 모든 요청을 가장 먼저 받아 적절한 컨트롤러에 위임하고, 그 결과를 클라이언트에게 응답하는 역할을 한다.
1.1 Front Controller 패턴
Front Controller 패턴은 모든 요청을 하나의 진입점에서 처리하는 디자인 패턴이다.
[Before - 각 요청마다 개별 서블릿]
/user → UserServlet
/order → OrderServlet
/product → ProductServlet
[After - Front Controller 패턴]
/user ↘
/order → DispatcherServlet → 적절한 Controller
/product ↗
장점:
- 모든 요청에 대한 공통 처리 로직 적용 가능 (인증, 로깅 등)
- 중복 코드 제거
- 요청 흐름의 일관성 유지
1.2 DispatcherServlet의 위치
+-------------------+
HTTP 요청 → | DispatcherServlet |
+-------------------+
↓
+-------------------+
| HandlerMapping |
+-------------------+
↓
+-------------------+
| HandlerAdapter |
+-------------------+
↓
+-------------------+
| Controller |
+-------------------+
↓
+-------------------+
| ViewResolver |
+-------------------+
↓
+-------------------+
| View |
+-------------------+
↓
HTTP 응답
2. Spring MVC의 요청 처리 과정 (생명주기)
DispatcherServlet이 요청을 처리하는 과정을 단계별로 살펴보자.
2.1 요청 처리 흐름
1. 클라이언트 요청 → DispatcherServlet
2. DispatcherServlet → HandlerMapping (어떤 컨트롤러가 처리할지 결정)
3. DispatcherServlet → HandlerAdapter (컨트롤러 실행)
4. Controller → 비즈니스 로직 처리 → ModelAndView 반환
5. DispatcherServlet → ViewResolver (뷰 이름으로 실제 뷰 찾기)
6. View → 렌더링 → 클라이언트 응답
2.2 상세 흐름 설명
Step 1: 요청 수신
클라이언트로부터 HTTP 요청이 들어오면 DispatcherServlet이 가장 먼저 요청을 받는다.
// web.xml 설정 예시
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Step 2: HandlerMapping
DispatcherServlet은 HandlerMapping에게 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
// @RequestMapping 기반 매핑
@Controller
public class UserController {
@GetMapping("/users/{id}")
public String getUser(@PathVariable Long id, Model model) {
// ...
}
}
주요 HandlerMapping 구현체:
- RequestMappingHandlerMapping: @RequestMapping 어노테이션 기반
- BeanNameUrlHandlerMapping: 빈 이름으로 URL 매핑
- SimpleUrlHandlerMapping: URL 패턴과 핸들러를 직접 매핑
Step 3: HandlerAdapter
HandlerMapping이 찾은 핸들러를 실행할 수 있는 HandlerAdapter를 조회하고, 핸들러를 실행한다.
// 다양한 형태의 컨트롤러를 지원
// 1. @Controller 방식
@Controller
public class MyController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
// 2. Controller 인터페이스 방식 (구버전)
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) {
return new ModelAndView("hello");
}
}
Step 4: 컨트롤러 실행 및 ModelAndView 반환
컨트롤러가 비즈니스 로직을 처리하고 결과를 ModelAndView 객체로 반환한다.
@Controller
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/products/{id}")
public String getProduct(@PathVariable Long id, Model model) {
Product product = productService.findById(id);
model.addAttribute("product", product); // Model에 데이터 추가
return "product/detail"; // View 이름 반환
}
}
Step 5: ViewResolver
DispatcherServlet은 ViewResolver를 통해 View 이름을 실제 View 객체로 변환한다.
// application.properties
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
// "product/detail" → "/WEB-INF/views/product/detail.jsp"
주요 ViewResolver:
- InternalResourceViewResolver: JSP 뷰 처리
- ThymeleafViewResolver: Thymeleaf 템플릿 처리
- ContentNegotiatingViewResolver: 요청 Accept 헤더에 따른 뷰 선택
Step 6: View 렌더링
View가 Model 데이터를 사용해 최종 응답을 생성하고 클라이언트에게 반환한다.
3. DispatcherServlet 소스 분석
3.1 핵심 메서드: doDispatch()
DispatcherServlet의 핵심 로직은 doDispatch() 메서드에 있다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response)
throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2. 핸들러 어댑터 조회
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 인터셉터 preHandle 실행
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 4. 핸들러(컨트롤러) 실행
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 5. 인터셉터 postHandle 실행
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
// 6. 결과 처리 (뷰 렌더링)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
3.2 주요 컴포넌트 초기화
DispatcherServlet은 초기화 시점에 다양한 전략 컴포넌트들을 설정한다.
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context); // 파일 업로드 처리
initLocaleResolver(context); // 지역 정보 처리
initThemeResolver(context); // 테마 처리
initHandlerMappings(context); // 핸들러 매핑
initHandlerAdapters(context); // 핸들러 어댑터
initHandlerExceptionResolvers(context); // 예외 처리
initRequestToViewNameTranslator(context); // 뷰 이름 변환
initViewResolvers(context); // 뷰 리졸버
initFlashMapManager(context); // 플래시 맵 관리
}
3.3 getHandler() 메서드
요청에 맞는 핸들러를 찾는 메서드다.
protected HandlerExecutionChain getHandler(HttpServletRequest request)
throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
여러 HandlerMapping을 순회하면서 요청을 처리할 수 있는 핸들러를 찾는다.
4. 인터셉터 (Interceptor)
DispatcherServlet은 인터셉터를 통해 요청 처리 전후에 공통 로직을 수행할 수 있다.
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 컨트롤러 실행 전
log.info("Request URL: {}", request.getRequestURL());
return true; // true면 계속 진행, false면 중단
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// 컨트롤러 실행 후, 뷰 렌더링 전
log.info("Controller executed");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 뷰 렌더링 완료 후
log.info("Request completed");
}
}
인터셉터 등록:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/static/**", "/error");
}
}
5. 예외 처리
DispatcherServlet은 HandlerExceptionResolver를 통해 예외를 처리한다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse("NOT_FOUND", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
ErrorResponse error = new ErrorResponse("INTERNAL_ERROR", "서버 오류가 발생했습니다");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
6. 정리
DispatcherServlet 핵심 역할
| 역할 | 설명 |
|---|---|
| Front Controller | 모든 HTTP 요청의 진입점 |
| 요청 라우팅 | HandlerMapping을 통해 적절한 컨트롤러로 요청 전달 |
| 컨트롤러 실행 | HandlerAdapter를 통해 다양한 형태의 컨트롤러 지원 |
| 뷰 렌더링 | ViewResolver를 통해 뷰 처리 |
| 예외 처리 | HandlerExceptionResolver를 통해 예외 처리 |
Spring Boot에서의 자동 설정
Spring Boot는 DispatcherServlet을 자동으로 설정한다.
// DispatcherServletAutoConfiguration에서 자동 등록
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(
webMvcProperties.isThrowExceptionIfNoHandlerFound());
return dispatcherServlet;
}
DispatcherServlet을 이해하면 Spring MVC의 동작 원리를 깊이 있게 파악할 수 있고, 문제 해결과 성능 최적화에 큰 도움이 된다.
Comments