4 minute read

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의 동작 원리를 깊이 있게 파악할 수 있고, 문제 해결과 성능 최적화에 큰 도움이 된다.

Categories:

Updated:

Comments