Blog
스터디
CS Study with SON
14주차
디스패처 서블릿

출처 - https://github.com/jmxx219/CS-Study (opens in a new tab)

DispatcherServlet

서블릿 참고 (opens in a new tab)


DispatcherServlet이란?

  • 스프링 MVC가 사용하는 핵심 개념
  • HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러로 정의함
    • 클라이언트로부터 어떤 요청이 들어오면 서블릿 컨테이너가 요청을 받는데, 이때 공통된 작업은 디스패처 서블릿에서 처리하고, 이외 작업은 다른 세부 컨트롤러로 위임함
  • 과거에는 모든 서블릿을 URL 매핑을 위해 web.xml에 모두 등록해주어야 했음
    • DispatcherServlet의 등장으로 애플리케이션으로 들어오는 모든 요청을 핸들링해주고 공통 작업을 처리하면서 web.xml의 의존을 낮출 수 있게 됨

상속 구조(계층 구조)

  • DispatcherServletFrameworkServletHttpServletBeanHttpServletGenericServletServlet

    public class DispatcherServlet extends FrameworkServlet {
    }
     
    public abstract class FrameworkServlet extends HttpServletBean {
    }
     
    public abstract class HttpServletBean extends HttpServlet {
    }
  • 디스패처 서블릿도 서블릿 (opens in a new tab)의 일종으로, HttpServlet을 상속함


프론트 컨트롤러 패턴(Front Controller Pattern)

  • 도입 배경
    • MVC 패턴 중 컨트롤러가 많아지고, 컨트롤러에서 공통으로 처리해야 하는 부분이 증가한다면 코드 중복이 발생함
      • 각 컨트롤러마다 공통 로직을 만들어야 했음
    • 프론트 컨트롤러 도입 후에는 공통 로직은 프론트 컨트롤러에서 처리하고, 각자 처리해야하는 로직은 각 컨틀롤러에 처리함
      • 공통의 관심사를 별도로 모으는 역할
  • 특징
    • 서블릿 하나로 클라이언트의 요청을 모두 받음
    • 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출함
    • 공통 처리 가능
    • 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨 ➡ 프론트 컨트롤러가 나머지 컨트롤러를 직접 호출해주기 때문
    • 스프링 웹 MVC의 핵심으로, DispatcherServlet이 Front Controller 패턴으로 구현됨
      • Spring 공식 문서 (opens in a new tab)에서 "Spring MVC, as many other web frameworks, is designed around the front controller pattern where a central Servlet, the DispatcherServlet, provides a shared algorithm for request processing"이라고 명시하고 있음
  • 도입 이후 처리과정
    1. 클라이언트 - HTTP 요청
    2. Front Controller - URL 매핑 정보에서 컨트롤러 조회 후 컨트롤러 호출
    3. Controller - ModelAndView 반환
    4. Front Controller - ViewResolver 호출
    5. ViewResolver - View*반환
    6. Front Controller - 클라이언트에 View 응답을 전달

프론트 컨트롤러 패턴의 역할

  1. 클라이언트로 부터 받은 요청이 어떤 Controller에 대한 요청인지 맵핑
  2. 해당 Controller에 대한 요청을 처리 후 Response를 ModelView로 반환
Map<String, String> param = createParamMap(request):
ModelView mv = controller.process(param);
  1. 반환된 ModelView를 통해 적절한 View를 찾음(ViewResolver)
  2. View를 찾았다면 해당 View를 통해 render()를 호출해서 view를 렌더링함

어댑터 패턴(Adapter Pattern)

Front Controller와 Adapter Pattern의 관계

Spring MVC의 DispatcherServlet은 Front Controller 패턴으로 구현되어 있으며, 내부적으로 Adapter Pattern을 활용하여 다양한 타입의 핸들러를 유연하게 처리합니다. 즉, Adapter Pattern은 Front Controller가 여러 종류의 컨트롤러/핸들러를 지원하기 위한 내부 구현 메커니즘입니다.

  • 배경 및 필요성

    • DispatcherServlet(Front Controller)은 중앙에서 모든 요청을 받지만, 실제 처리는 다양한 타입의 핸들러가 담당함
      • @Controller@RequestMapping 메서드
      • Controller 인터페이스를 구현한 클래스
      • HttpRequestHandler 인터페이스를 구현한 클래스
      • Servlet 인터페이스를 구현한 클래스 등
    • 이처럼 각각의 핸들러 타입은 완전히 다른 인터페이스를 가지므로, DispatcherServlet이 직접 호출하기 어려움
    • HandlerAdapter를 도입하여 DispatcherServlet은 핸들러의 구체적인 타입을 알 필요 없이 일관된 방식으로 핸들러를 호출할 수 있게 됨
  • 특징

    • 어댑터 패턴은 호환되지 않는 인터페이스들을 연결하는 디자인 패턴으로, 기존의 클래스를 수정하지 않고도 특정 인터페이스를 필요로 하는 코드에서 사용할 수 있게 도와줌
      • HandlerAdapter는 핸들러 객체와 DispatcherServlet 사이의 브리지 역할을 수행함
      • 각 HandlerAdapter 구현체는 supports(Object handler) 메서드로 처리 가능 여부를 판단하고, handle() 메서드로 실제 핸들러를 호출함
    • 스프링 MVC의 HandlerAdapter 인터페이스가 바로 어댑터 패턴을 사용함
      • 스프링 MVC가 사용하는 어댑터의 이름이 컨트롤러 어댑터가 아니라 핸들러 어댑터인 이유는 컨트롤러뿐만 아니라, 어댑터가 지원하기만 하면 어떤 것이라도 URL에 매핑하여 사용할 수 있기 때문
  • 도입 이후 처리과정

    1. 클라이언트 - HTTP 요청
    2. Front Controller
      • Handler Mapping 정보에서 핸들러 조회
      • 핸들러를 처리할 수 있는 Handler Adapter 조회
      • 핸들러 어댑터의 handle(handler) 호출
    3. Handler Adapter
      • handler(실제 Controller) 호출
      • ModelView 반환
    4. Front Controller - ViewResolver 호출
    5. ViewResolver - View 반환
    6. Front Controller - 클라이언트에 View 응답을 전달

실제 내부 구현

Spring MVC에서는 다음과 같은 HandlerAdapter 인터페이스를 기반으로 다양한 어댑터 구현체가 사용됩니다.

public interface HandlerAdapter {
	boolean supports(Object handler);
 
	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
 
    @Deprecated
	long getLastModified(HttpServletRequest request, Object handler);
}

주요 HandlerAdapter 구현체는 다음과 같습니다.

  • RequestMappingHandlerAdapter: @RequestMapping 어노테이션이 붙은 메서드 처리
  • SimpleControllerHandlerAdapter: Controller 인터페이스를 구현한 클래스 처리
  • SimpleServletHandlerAdapter: Servlet 인터페이스를 구현한 클래스 처리

각 어댑터의 구현 코드는 다음과 같습니다.

RequestMappingHandlerAdapter는 @RequestMapping 어노테이션이 붙은 메소드를 처리하는 어댑터이다. 직접적으로 HandlerAdapter interface를 implements하고 있지는 않지만 HandlerAdapter를 implements 하고 있는 AbstractHandlerMethodAdapter를 상속받아서 AbstractHandlerMethodAdapter의 handle() 메서드에서 handleInternal()을 쓰는 방식인 Template Method Pattern을 사용하고 있다.

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
 
    ...
 
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
 
        ModelAndView mav;
        checkRequest(request);
 
        // Execute invokeHandlerMethod in synchronized block if required.
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            } else {
                // No HttpSession available -> no mutex necessary
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            // No synchronization on session demanded at all...
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
 
        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            } else {
                prepareResponse(response);
            }
        }
 
        return mav;
    }
 
}

디스패처 서블릿의 동작 과정


디스패처 서블릿 동자 과정

  1. DispatcherServlet으로 클라이언트의 웹 요청(HttpServletRequest)이 들어옴
  2. 웹 요청을 HandlerMapping에 위임하여 해당 요청을 처리할 Handler(Controller)를 탐색
  3. 찾은 Handler를 실행할 수 있는 HandlerAdapter를 탐색
  4. 찾은 HandlerAdapter를 사용해서 Handler의 메서드를 실행
  5. 이때 Handler의 반환값은 ModelView
  6. View 이름을 ViewResolver에게 전달하고, ViewResolver는 해당하는 View 객체를 반환함
  7. DispatcherServlet은 View 에게 Model을 전달하고 화면 표시를 요청하는데, 이때 Model이 null이면 View를 그대로 사용함
    • 반면 값이 있으면 View에 Model 데이터를 렌더링함
  8. 최종적으로 DispatcherServlet은 View 결과(HttpServletResponse)를 클라이언트에게 반환함

위 흐름은 @Controller 기준이고, @RestController의 경우 6, 7과정이 생략된다.
즉, ViewResolver를 타지 않고 반환값에 알맞는 MessageConverter를 찾아 응답을 작성한다.



디스패처 서블릿 설정 방법

디스패처 서블릿을 구현하는 방법에는 2가지가 있다.

  1. web.xml에 DispatcherServlet 선언 방법

    • xml에 DispatcherServlet을 선언하고 URI로 요청 경로를 매핑하여 사용할 수 있음

    • 표준 J2EE 서블릿 설정 예시

      <web-app>
       
          <servlet>
              <servlet-name>example</servlet-name>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <load-on-startup>1</load-on-startup>
          </servlet>
       
          <servlet-mapping>
              <servlet-name>example</servlet-name>
              <url-pattern>*.form</url-pattern>
          </servlet-mapping>
       
      </web-app>
  2. DispatcherServlet 상속 후 @Webservlet 애노테이션 사용 방법

    @WebServlet(name = "helloServlet", urlPatterns = "/hello")
    public class HelloServlet extends DispatcherServlet {
    }
    • DispatcherServlet을 상속하여 @WebServlet 애노테이션에 urlPatterns를 지정하여 사용할 수 있음
    • @WebServlet 애노테이션을 사용하려면 Httpservlet을 상속해야 하는데 위에서 말했듯이 DispatcherServlet은 HttpServlet을 상속하기 때문에 서블릿 클래스에서 DispatcherServlet을 상속해도 문제 없음


Ref