Enthusiasm! Enthusiasm!

스프링 MVC 구조 이해 - 실제 코드 뜯어보기 본문

자바 스프링/스프링MVC

스프링 MVC 구조 이해 - 실제 코드 뜯어보기

열동 2023. 11. 9. 01:38

스프링 MVC구조에 대해 배우며 어느 정도 이해를 했고, 직접 코드를 작성하여 구조를 만들어 보니 스프링에서 실제로 어떻게 적용되고 있는지 궁금하였다. 이번 포스팅은 직접 스프링 라이브러리를 들여다보며 MVC 구조를 분석한 글이다.


서론 - 스프링 MVC 구조

다음은 스프링 MVC의 완성된 구조도이며, Sprign boot를 통해 프로젝트를 만들 때, Spring Web 의존성을 추가했다면, 다음과 같이 spring-webmvc라는 이름의 모듈이 라이브러리에 추가되어 있다. 각 구성요소의 역할과 실제 어떻게 구현되어 있는지 알아보자.


DispatcherServlet

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, while actual work is performed by configurable delegate components. This model is flexible and supports diverse workflows.

 

공식문서에 따르면, Spring MVC는 front controller pattern 구조를 이용하여 설계했으며, 이때 front controller 역할을 하는 것이 Servlet을 계승한 DispatcherServlet이라 한다. front controller pattern에 대해 간단히 설명하자면, 클라이언트의 모든 요청에 대해 front controller가 전부 처리하도록 중앙집중적으로 설계하는 디자인 패턴이다. 이와 같이 DispatcherServlet은 Spring MVC의 핵심으로 클라이언트의 모든 요청을 처리한다. 이제 HTTP 요청이 들어왔을 때, DispatcherServlet의 doDispatch 코드를 뜯어보며 요청을 처리하는 과정을 하나하나 살펴보자.


 

Handler

Handler는 넓은 의미에 무언가를 다루는 것으로, 어떤 클래스나 인터페이스로 정의되는 것이 아닌 개념적인 용어라 생각하면 된다. 즉 Controller는 Handler의 일종이고 HandlerAdapter를 통해 Controller 외에 어떤 것도 Adapter를 이용하여 지원할 수 있어서 Handler라고 이름을 지정하였다. 하지만 현재 MVC 구조에서 Handler는 Controller와 같은 개념이라 생각해도 된다.


Handler  Mapping

클라이언트를 통해 HTTP요청이 들어오면 DispatcherServlet은 Handler 조회를 통해 Handler를 매핑한다.

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

getHandler 메서드를 통해 매핑되는 핸들러를 가져온다. 이 때 스프링 빈에 등록된 핸들러들을 조회하며, 일반적으로 요청 URL에 매핑된 Handler, 즉 컨트롤러를 찾아 매핑할 것이다.


HandlerAdpater 목록 조회

가져온 Handler를 실행할 수 있는 HandlerAdapter를 조회한다.

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

코드를 들여다보면 HandlerAdapter 인터페이스의 supports 메서드를 이용한다.

boolean supports(Object handler);

이때 파라미터의 타입이 Object이기 때문에 모든 객체 타입을 받을 수 있다. 위에서 설명한 Handler의 개념과 연관된다.


HandlerAdapter 실행

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

HandlerAdapter의 handle 메서드를 실행하며, HandlerAdapter의 이때 ModelAndView를 반환한다.


ViewResolver 조회 및 render

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

ModelAndView에 담긴 View를 처리하는 코드이다. 이 메서드를 들여다보면 다음과 같은 로직이 있다.

if (mv != null && !mv.wasCleared()) {
    render(mv, request, response);
}

//render

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response){
    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        // We need to resolve the view name.
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {
           //예외처리
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
           //예외처리
        }
    }
}

render 메서드를 실행시키며, 이 떄 컨트롤러가 viewName만을 반환하는 경우와 View 객체를 직접 반환하는 경우를 나누어 처리한다.

viewName만 반환했을 경우 viewResolver를 통해 view를 매핑하는 과정을 거친다.

MVC모델에 대해 배우면서 ModelAndView를 직접 리턴해도 되고, viewName만을 리턴해도 되며, 스프링이 이를 알아서 처리해 준다고는 알고 있었지만, 코드를 뜯어보며 이 과정을 직접 보니 더 깊은 이해를 할 수 있었다.

view까지 처리하면 최종적으로 클라이언트에게 응답을 반환한다!


결론

스프링 MVC구조에 대해 이론적으로 배운 내용이 실제 스프링에서 어떻게 적용되고 있는지 궁금하여 코드를 들여다보았다. DispatcherServlet의 doDispatch 메서드를 뜯어보며 흐름을 파악할 수 있었다. 

이론적으로 배운 내용들과 구조가 동일하였으며, 글에는 작성하지 않았지만 코드를 뜯어보며 이전에 배운 Interceptor가 어떻게 적용되고 있는지도 알 수 있어서 좋았다!

'자바 스프링 > 스프링MVC' 카테고리의 다른 글

서블릿이 제공하는 HTTP 요청과 응답 처리  (0) 2023.04.05
웹 애플리케이션의 이해  (0) 2023.04.02
스프링 파일 업로드  (0) 2023.03.16
스프링 타입 컨버터  (0) 2023.03.15
스프링 예외처리  (0) 2023.03.15
Comments