ahlight
개발 저장소
ahlight
전체 방문자
오늘
어제
  • 분류 전체보기 (27)
    • Java (5)
    • Spring (5)
    • JPA (2)
    • JavaScript (0)
    • Computer Science (10)
      • 디자인패턴, 프로그래밍 패러다임 (1)
      • 네트워크 (4)
      • 운영체제 (4)
      • 데이터베이스 (1)
      • 자료구조 (0)
    • 알고리즘 (0)
    • 프로그래머스 (0)
    • 백준 (0)
    • 서평 (3)
    • 회고 (1)
    • TIL (0)
    • 기타 (1)

블로그 메뉴

  • 홈

공지사항

인기 글

태그

  • 라즈베리파이4 #홈서버 #포트포워딩 #dhcp
  • TDD
  • Java
  • 넥스트스텝
  • 클린코드

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
ahlight
Spring

Spring - DispatcherServlet에 대해 알아보자

Spring - DispatcherServlet에 대해 알아보자
Spring

Spring - DispatcherServlet에 대해 알아보자

2023. 7. 15. 00:42

1. DispatcherServlet?

 과거엔 view(사용자에게 보여지는 영역)단에서 비즈니스 로직을 처리해 코드가 서로 얽혀 유지보수에 어려움이 있었다. 이를 해결하기 위해 나온 패턴이 MVC(Model-View-Controller)패턴이다.

 

 초창기 MVC패턴에선 다양한 요청을 처리하기 위한 Controller가 많아지고 이에 따라 중복되고 공통된 기능을 하는 코드들이 늘어났다. 이를 해결하기 위해 나온 방법이 FrontController다.

 

 FrontController의 역할은 한 회사의 대표와 같다. 대표는 외부에서 들어오는 각각의 요청들을 적절하게 수행할 수 있는 직원들에게 분배한다. 또 본인 혼자서 수행하기에 업무양이 많다면 전무, 상무, 부장 등 간부들에게 지시를 내려 적합한 직원을 찾도록한다. 이렇듯 FrontController 또한 해당 요청에 적합한 Controller를 찾기위해 여러 객체들에게 지시를 내린다.이러한 역할을 하는 FrontController가 바로 스프링의 DispatcherServlet이다.

 

2. DispatcherServlet의 역할

  • 요청이 들어오면 DispatcherServlet은 HandlerMapping을 통해 요청 url에 매핑된 Controller를 찾는다.
  • 그리고 해당 Controller에 적합한 Adapter가 존재하는지 목록을 확인하고 존재할 경우 해당 어댑터를 통해 매핑된 Controller를 호출한다.
  •  마지막으로 Controller에서 처리된 반환 값을 viewResolver에게 보내 view를 반환한다.

 

3. DispatcherServlet의 동작 과정

DispatcherSerlvet 또한 아래 그림처럼 최고 조상에 Servlet 인터페이스를 상속 받는다. 그렇기 때문에 Servlet와 같은 라이프 사이클을 갖게 된다.

3.1 초기화(init)

서블릿과 마찬가지로 최초 요청이 오면 초기화를 먼저 진행한다. 

 

Servlet의 init()이 실행된다. 이 init은 GenericServlet을 거쳐 HttpServletBean에서 오버라이딩 되고 initServletBean()을 호출하게 된다.

<Servlet>
public interface Servlet {
void init(ServletConfig config) throws ServletException;
                                .
                                .
                                .
}

<GenericServlet>                             
@Override
public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
}

// 실제 오버라이딩하게 되는 메서드 -> init(config)에서 호출이 되기때문이다.
public void init() throws ServletException {
    // NOOP by default
}

<HttpServletBean>
@Override
public final void init() throws ServletException {
                                .
                                .
                                .
    initServletBean();
}

protected void initServletBean() throws ServletException {
}

 

FrameworkSevlet에서는 initServletBean()을 오버라이딩하고 initWebApplicationContext()을 호출하고 여기선 orRefresh()를 호출한다.

 

<FrameworkServlet>
@Override
protected final void initServletBean() throws ServletException {
                                .
                                .
                                .
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
                                .
                                .
                                .
}

protected WebApplicationContext initWebApplicationContext() {
                                .
                                .
                                .                                  
    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        synchronized (this.onRefreshMonitor) {
            onRefresh(wac);
        }
    }
                                .
                                .
                                .
    return wac;
 }

onrefresh()는 Spring context(빈 관리)를 refresh한다음 호출된다.
protected void onRefresh(ApplicationContext context) {
    // For subclasses: do nothing by default.
}

 

onRefresh는 드디어 DispatcherServlet에서 오버라이딩 되고  initStrategies(context)를 호출한다. 

/**
 * This implementation calls {@link #initStrategies}.
 */
@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

initStrategies라는 이름과  아래 호출되는 메서드명들을 보면 알 수 있듯이 앞으로 DispatcherServlet이 위임하는 작업을 수행할 객체를 초기화를 하게 된다. 

 

3.2 요청 수행(service)

서블릿과 마찬가지로 요청이 들어오면  service()를 수행한다.

 

Servlet의 service()는 GenericServlet을 거쳐 HttpServlet에서 오버라이딩 되고 여기서 doGet(), doPost()등을 호출하게 된다.

<HttpServelt>
// 오버라이딩 된 service에서 오버로딩된 service를 호출
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
									 .
                                     .
                                     .                                  
    service(request, response);
}

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                // Invalid date header - proceed as if none was set
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }

    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);

    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);

    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);

    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req, resp);

    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req, resp);

    } else {
       										.
                                            .
                                            .
    }
}

 

 

doGet, doPost 등은  FrameworkServlet에서 오버라이딩 되고 이러한 do~()시리즈의 메서드는 모두 processRequest(res, reps)를 호출하게 된다. 그리고 processRequest는 doService()를 호출한다.

 

<FrameworkServlet>
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    processRequest(request, response);
}

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    processRequest(request, response);
}

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
											.
                                      	    .
                                            .                                     
    try {
        doService(request, response);
    }
    										.
                                            .
                                            .
    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

 

doService는 DispatcherServlet에서 오버라이딩 되고 doDispatch를 호출해 요청을 처리하게 된다.

 

<DispatcherServlet>
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    											.
                                                .
                                                .
    try {
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
        if (this.parseRequestPath) {
            ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
        }
    }
}

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = HttpMethod.GET.matches(method);
            if (isGet || HttpMethod.HEAD.matches(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

 

4. doDispatch() 이후

doDIspatch()에서는 요청url에 매핑된 Controller를 찾게 된다. 그리고 해당 컨트롤러에 맞는 핸들러 어댑터를 통해 Controller를 호출 한다. Controller에서 처리된 반환값은 ModelAndView에 담겨 반환되고 viewResolver에 전해져 view를 렌더링하게 된다.

 

아래 이미지와 같은 모든 작업을 DispatcherServlet에서 수행하게 되고 그 안에서도 doDispatch()메서드를 통해 수행된다.

김영한의 Spring 강의 중

 

'Spring' 카테고리의 다른 글

Spring - @Transactional 활용해 멀티 스레드 환경을 테스트할 때 주의점  (0) 2024.05.06
Spring - 요청 파라미터 LocalDate타입 필드에 바인딩 및 검증  (1) 2024.04.12
Spring - Springboot + Vue3 + MariaDB를 카페24에 배포할 때 Tip(주의사항)  (1) 2023.07.26
Spring - ArgumentResolver  (0) 2023.07.22
  • 1. DispatcherServlet?
  • 2. DispatcherServlet의 역할
  • 3. DispatcherServlet의 동작 과정
  • 4. doDispatch() 이후
'Spring' 카테고리의 다른 글
  • Spring - @Transactional 활용해 멀티 스레드 환경을 테스트할 때 주의점
  • Spring - 요청 파라미터 LocalDate타입 필드에 바인딩 및 검증
  • Spring - Springboot + Vue3 + MariaDB를 카페24에 배포할 때 Tip(주의사항)
  • Spring - ArgumentResolver
ahlight
ahlight

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.