티스토리 뷰

Spring

DispatcherServlet

개발하고싶은개발자 2023. 11. 18. 16:01

HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해 주는 프론트 컨트롤러(Front Controller)

즉, 클라이언트로부터 어떠한 요청이 오면 Tomcat과 같은 서블릿 컨테이너가 요청을 받게 되는데 이 모든 요청을 프론트 컨트롤러인 디스패처 서블릿이 가장 먼저 받게 된다. 그러면 디스패처 서블릿은 공통적인 작업을 먼저 처리한 후에 해당 요청을 처리해야 하는 컨트롤러를 찾아서 작업을 위임한다.

 

 

 

Front Controller(프론트 컨트롤러)

  • 주로 서블릿 컨테이너의 제일 앞에서 서버로 들어오는 클라이언트의 모든 요청을 받아서 처리해 주는 컨트롤러로써, MVC 구조에서 함께 사용되는 디자인 패턴이다.

 

 

 

 

DispatcherServlet의 동작 과정

  1. 클라이언트의 요청을 디스패처 서블릿이 받음
  2. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾음
  3. 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달함
  4. 핸들러 어댑터가 컨트롤러로 요청을 위임함
  5. 비지니스 로직을 처리함
  6. 컨트롤러가 반환값을 반환함
  7. 핸들러 어댑터가 반환값을 처리함
  8. 서버의 응답을 클라이언트로 반환함

위의 8단계의 과정을 DispatcherServlet 중심으로 자세히 알아보자

 

 

 

1. 클라이언트의 요청을 디스패처 서블릿이 받음

DispatcherServlet은 Spring이 HttpServlet을 Spring에 맞게 요청을 처리할 수 있도록 구현한 클래스이다. 따라서 요청이 오면 필터들을 지나 DispatcherServlet으로 오게 된다.

실제로는 등록된 필터 목록을 보면 Tomcat WebSocker Filter가 마지막으로 등록돼서 이 필터에서 DispatcherServlet의 요청을 처리하는 메소드인 service()를 호출한다(실제로는 HttpServlet의 service를 호출하고 HttpServlet에서 DispatcherServlet의 doDispatcher()를 호출한다)

 

 

2. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾음

DispatcherServlet은 요청을 처리할 수 있는지 판단을 HandlerMapping을 통해서 하는데 이 HandlerMapping 구현체 중 RequestMappingHandlerMapping 클래스에 컨트롤러 정보가 있다.

이 컨트롤러 정보는 처음 애플리케이션이 실행될 때 Bean 중에서 @Controller 또는 @RequestMapping 어노테이션이 붙어 있는 빈들을 통해서 요청을 처리할 핸들러 목록을 찾아서 저장해둔 정보이다(AbstractHandlerMethodMapping의 register() 메소드가 호출되며 저장된다). 이 때 요청에 따른 메소드 정보를 HashMap으로 저장한다. 이 HashMap의 key는 RequestMappingInfo 클래스이고, value는 MappingRegistration 클래스이다.

따라서 DispatcherServlet에서 위임할 컨트롤러를 찾을 때 이 RequestMappingHandlerMapping에서 요청 정보에 따른 MappingRegistration 정보를 찾아서 MappingRegistration 내부에 있는 메소드 정보(HandlerMethod)를 가지고 HandlerExecutionChain을 만들어서 리턴한다. 이때 HandlerExecutionChain을 만들 때 관련 있는 interceptor들도 연결해서 리턴한다.

// DispatcherServlet.java
// DispatcherServlet에서 HandlerMappings을 통해 HandlerExecutionChain을 얻어오는 코드

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;
}

 

 

 

3. 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달함

이제 컨트롤러로 요청을 위임해야 하는데, 컨트롤러로 요청을 직접 위임하는 게 아니라 HandlerAdapter를 통해 위임한다. 컨트롤러의 구현 방식이 다양하기 때문에 직접 위임하는 것이 아니라 Adapter 패턴을 적용한 HandlerAdapter를 통해 위임하는 것이다.

// DispatcherServlet.java
// DispatcherServlet에 있는 HandlerAdapter 중 현재 요청을 처리할 수 있는 Adapter를 리턴한다

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

 

 

 

 

4. 핸들러 어댑터가 컨트롤러로 요청을 위임함

핸들러 어댑터가 컨트롤러로 요청을 위임하기 전/후에 공통적인 전/후처리 과정이 필요하다. 대표적으로 인터셉터들을 포함해 요청 시에 @RequestParam, @RequestBody 등을 처리하기 위한 ArgumentResolver들과 응답 시에 ResponseEntity의 Body를 Json으로 직렬화하는 등의 처리를 하는 ReturnValueHandler 등이 핸들러 어댑터에서 처리된다. 어댑터에는 ArgumentResolver의 리스트를 갖고 있는데 이 중 들어온 요청을 처리할 수 있는 ArgumentResolver를 찾아서 해당 ArgumentResolver로 요청을 처리해준다. 마찬가지로 ReturnValueHandler 리스트 중 처리할 수 있는 ReturnValueHandler로 응답을 처리한다.

이 때 컨트롤러로 위임할 때 리플렉션을 이용해서 찾은 컨트롤러의 메소를 호출하게 된다. (2번에서 언급했던 @Controller, @RequestMapping 빈들을 등록할 때 메소드 정보도 같이 등록된다)

 

 

5. 비지니스 로직을 처리함

우리가 작성한 비지니스 로직이 처리된다

 

 

6. 컨트롤러가 반환값을 반환함

컨트롤러에서 반환값이 있을 경우 반환값을 리턴한다

 

 

7. 핸들러 어댑터가 반환값을 처리함

위에서 설명한대로 컨트롤러가 응답을 반환하게 되면 HandlerAdapter는 컨트롤러로부터 받은 응답을 ReturnValueHandler로 후처리한 후에 디스패처 서블릿으로 돌려준다. 만약 컨트롤러가 ResponseEntity를 반환하면 HttpEntityMethodProcessor가 MessageConverter를 사용해 응답 객체를 직렬화하고 응답 상태(HttpStatus)를 설정한다. 만약 컨트롤러가 View 이름을 반환하면 ViewResolver를 통해 View를 반환한다.

// ServletInvocableHandlerMethod.java
// 리플렉션을 통해 메소드가 호출될 때 아래 메소드가 호출되는데 6라인을 보면 호출된 뒤 응답값을 받아서 
// 24라인에서 ReturnValueHandler 중 응답 값을 처리할 수 있는 ReturnValueHandler에서 처리하도록 한다


public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);

    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}

 

 

 

8. 서버의 응답을 클라이언트로 반환함

디스패처 서블릿을 통해 반환되는 응답은 다시 필터들을 거쳐 클라이언트에게 반환된다. 이때 응답이 데이터라면 그대로 반환되지만, 응답이 화면이라면 View의 이름에 맞는 View를 찾아서 반환해 주는 ViewResolver가 적절한 화면을 내려준다.

 

 

 

이렇게 스프링에서는 Servlet을 직접적으로 사용하지 않더라도 서블릿을 기반으로 작동하기 때문에 WAS인 Tomcat이 필요하다.

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30