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