Chapter 10 - Spring MVC 프레임워크 동작 방식

이현빈·2024년 3월 23일

1. Spring MVC 핵심 구성 요소

Spring MVC에서의 웹 클라이언트 요청 처리 과정

  1. 웹 클라이언트의 요청을 DispaterServlet로 전달
  2. DispatcherServletHanderMapping Bean에게 Controller 검색을 요청하고, HandlerMapping은 클라이언트 요청 경로를 처리할 Controller를 DispatcherServlet에게 리턴
  3. DispatcherServletHandlerAdapter Bean에게 2.에서 찾은 Controller 객체 처리를 위임
  4. HandlerAdapter는 Spring Bean으로 등록된 Controller로부터 알맞은 메서드를 호출하여 실질적인 요청 처리 작업 수행
  5. 4.에서의 처리 결과를 HandlerAdapter에게 다시 리턴
  6. HandlerAdapter는 5.에서 받아온 요청 처리 결과를 ModelAndView 객체로 변환하여 DispatcherServlet에게 리턴
  7. DispatcherServletViewResolver Bean으로 5.에서 받은 처리 결과를 보여줄 View 검색, ViewResolver는 5.에서 받은 ModelAndView 객체에 있는 View 이름을 갖는 View 객체를 찾거나 생성하여 DispatcherServlet에게 리턴
  8. DispatcherServlet은 7.에서 리턴받은 View 객체에게 응답 요청
  9. 8.의 View 객체는 JSP를 실행하여 웹 클라이언트에게 전송할 응답 결과 생성

Spring MVC의 핵심 구성 요소와 각 요소 간 관계

이처럼 웹 클라이언트의 요청 처리 과정에는 DispaterServlet, HandlerMapping, HandlerAdapter, Controller, ViewResolver, View, JSP가 각자의 역할을 수행하면서 관여한다.

Controller와 Handler

  • Controller
    웹에서의 요청을 처리하고 그 결과를 View로 전달하는 Spring Bean 객체
  • Handler
    웹에서의 요청을 실제로 처리하는 객체로, Spring MVC에서는 @Controller 적용 객체, Controller 인터페이스 구현 객체, HttpRequestHandler 등의 객체를 모두 포괄한다.
    (즉, Handler 객체 타입이 항상 @Controller 적용 클래스 타입인 것은 아님)

2. DispatcherServlet과 Spring 컨테이너

web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">

  <servlet>
    <!-- 1. 지정한 이름으로 DispatcherServlet 등록 -->
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    
    <!-- 2. DispatcherServlet으로 웹 어플리케이션용 Spring 컨테이너 클래스 생성 -->
    <init-param>
      <param-name>contextClass</param-name>
      <param-value>
        org.springframework.web.context.support.AnnotationConfigWebApplicationContext
      </param-value>
    </init-param>
    
    <!-- 3. DispatcherServlet이 사용할 Spring 설정 클래스 지정 -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>
        config.MvcConfig
        config.ControllerConfig
      </param-value>
    </init-param>
    
    <!-- 4. tomcat 등으로 웹 어플리케이션 구동 시 이 servlet도 실행하도록 설정 -->
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- 5. 지정한 DispaterServlet이 클라이언트의 모든 요청을 처리 -->
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  ... 생략

</web-app>

DispatcherServlet으로 Spring 컨테이너를 생성하고, 생성한 Spring 컨테이너로부터 HandlerMapping, HandlerAdaper, Controller, ViewResolver 등 웹에서의 요청을 처리하는 작업을 수행하는 데 필요한 Bean을 제공받는다. 따라서 DispatcherServlet이 사용하는 Spring 설정 클래스에는 이러한 Bean들에 관해 정의되어 있어야 한다.


3. @Controller를 위한 HandMapping과 HandlerAdapter

DispatcherServlet은 웹 브라우저의 요청을 처리할 핸들러 객체를 찾는 HandlerMapping과 핸들러를 실행하기 위한 HandlerAdapter를 사용한다. 이를 위해 DispatcherServlet은 Spring 컨테이너로부터 해당 타입의 Bean을 구할 수 있어야 하므로 Spring 설정 클래스에는 두 타입에 관한 Bean 메서드가 모두 정의되어 있어야 한다.

하지만 @EnableWebMvc 어노테이션을 Spring 설정 클래스에 사용한 경우 두 클래스를 Bean으로 등록하는 코드를 별도로 작성할 필요가 없다. 이 어노테이션에 의해 추가되는 Spring Bean 중에는 다음의 두 클래스도 포함되어 있기 때문이다.

  • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
    : @Controller 어노테이션이 적용된 객체의 요청 매핑 어노테이션(@GetMapping, @PostMapping, @RequestMapping 등)의 값으로 지정된 요청 경로를 이용해서 웹 브라우저의 요청을 처리할 컨트롤러 Bean을 탐색한다.
  • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdaptor
    : 컨트롤러의 메서드를 알맞게 실행하고 그 결과를 ModelAndView 객체로 변환해서 DispatcherServlet에 리턴한다.

4. WebMvcConfigurer 인터페이스를 이용한 MVC 설정

@EnableWebMvc을 Spring 설정 클래스에 사용하면 @Controller 어노테이션이 붙은 컨트롤러 클래스에 관한 설정을 생성한다. 그리고 @EnableWebMvc이 붙은 설정 클래스가 WebMvcConfigurer 인터페이스를 상속했을 경우 @Configuration이 붙은 설정 클래스 또한 해당 인터페이스 타입의 Bean으로 등록되어 MVC 설정을 추가 생성한다. 이 MVC 설정은 WebMvcConfigurer 인터페이스에 정의된 메서드를 오버라이딩하여 구현하는 방식으로 수행된다.

MvcConfig 클래스

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
	/* 컨트롤러로 처리 불가능한 HTTP 요청을 DispatcherServlet의 초기값에 관한 Handler로 매핑 */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

	/* ViewResolver로 응답 결과 생성 시 지정한 경로에 있는 jsp 파일을 view 코드로 사용하도록 설정 */
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/view/", ".jsp");
    }
}

ViewResolver 설정

ViewResolver에 관한 세부 설정은 WebMvcConfigurer 인터페이스에서 정의된 configureViewResolvers()를 Spring 설정 클래스의 Bean 메서드로 구현하는 방식으로 수행한다. 예를 들어, ViewResolver로 JSP를 위한 View 객체를 생성한다면 ViewResolverRegistry 클래스의 jsp() 메서드를 통해 view 코드로 사용할 jsp 파일이 저장된 디렉토리 경로를 지정하고, 확장자를 .jsp로 지정하는 방식으로 ViewResolver를 설정한다.

Default Handler 설정

디폴트 핸들러(Default Handler)

디폴트 핸들러(Default Handler)는 웹 어플리케이션 서버가 제공하는 Default Servlet의 매핑 경로를 처리하기 위한 핸들러 객체를 말한다. @EnableWebMvc 어노테이션에 의해 등록된 HandlerMapping 객체는 @Controller 어노테이션이 적용된 Bean 객체가 처리 가능한 요청 경로만 처리할 수 있는데, 디폴트 핸들러는 해당 Controller가 처리할 수 없는 나머지 요청 처리를 위한 Handler를 제공하여 HandlerMapping의 단점을 보완한다.

configureDefaultServletHandling()에서 활용한 메서드와 객체

  • DefaultServletHandlerConfigurer.enable() 메서드
    이 메서드를 통해 DefaultServletHttpRequestHandler, SimpleUrlHandlerMapping Bean 객체를 추가한다.
  • DefaultServletHttpRequestHandler
    클라이언트의 모든 요청을 웹 어플리케이션 서버가 제공하는 Default Servlet이 처리하도록 위임한다.
  • SimpleUrlHandlerMapping
    별도의 설정이 없는 모든 요청 경로를 DefaultServletHttpRequestHandler 등을 이용하여 일괄적으로 처리하도록 설정할 때 사용한다.

HandlerMapping의 우선순위

HandlerMapping에서의 우선순위 산정 방식은 기본적으로 @Controller 어노테이션을 사용한 컨트롤러 객체가 존재하는 요청 경로를 처리하는 HandlerMapping을 우선적으로 적용되고, 그 외 나머지 요청 경로에 관한 핸들러를 매핑하는 SimpleUrlHandlerMapping이 가장 나중에 적용되는 방식을 취한다. 우선순위가 같은 HandlerMapping 간 적용 순서는 항상 고정된 순서를 보장하지 않을 수도 있다.

이러한 HandlerMapping의 우선순위는 AbstractHandlerMapping 클래스를 상속한 HandlerMapping 객체의 setOrder() 메서드를 활용하여 직접 설정하거나, getOrder() 메서드를 통해 그 순위를 직접 확인할 수 있다. 이때, 우선순위의 값이 작을수록 먼저 적용되고, 클수록 나중에 적용된다.


5. 직접 설정하기: @EnableWebMvc 미사용 시

아래 MvcConfig 클래스와 MvcConfigForNoEnableWebMvc 클래스에서의 설정이 갖는 의미는 서로 동일하다.
MvcConfig 클래스

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
	/* 컨트롤러로 처리 불가능한 HTTP 요청을 DispatcherServlet의 초기값에 관한 Handler로 매핑 */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

	/* ViewResolver로 응답 결과 생성 시 지정한 경로에 있는 jsp 파일을 view 코드로 사용하도록 설정 */
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/view/", ".jsp");
    }
}

MvcConfigForNoEnableWebMvc 클래스

@Configuration
public class MvcConfigForNoEnableWebMvc {
    /* handlerMapping() 
    	: 요청 처리 가능한 컨트롤러가 있으면 해당 컨트롤러에 의한 처리를 우선 수행*/
    @Bean
    public HandlerMapping handlerMapping() {
        RequestMappingHandlerMapping hm = new RequestMappingHandlerMapping();
        hm.setOrder(0);	// 최우선 처리하도록 지정
        return hm;
    }


    /* handlerAdapter() : HandlerAdapter의 기능을 수행할 Bean 객체 생성 */
    @Bean
    public HandlerAdapter handlerAdapter() {
        return new RequestMappingHandlerAdapter();
    }


    /* 이하 3개 메서드를 종합하여 MvcConfig.configureDefaultServletHandling()의 역할 수행 */
    /* simpleHandlerMapping() 
        : 컨트롤러로 처리 불가능한 요청은 DispatcherServlet의 초기 경로("/")에 관한 Handler로 매핑 */
    @Bean
    public HandlerMapping simpleHandlerMapping() {
        SimpleUrlHandlerMapping hm = new SimpleUrlHandlerMapping();
        Map<String, Object> pathMap = new HashMap<>();
        pathMap.put("/**", defaultServletHandler());
        hm.setUrlMap(pathMap);
        return hm;
    }
    /* defaultServletHandler(), requestHandlerAdapter()
        : 컨트롤러로 처리 불가능한 나머지 HTTP 요청을 처리할 Handler 생성 */
    @Bean
    public HttpRequestHandler defaultServletHandler() {
        return new DefaultServletHttpRequestHandler();
    }
    @Bean
    public HandlerAdapter requestHandlerAdapter() {
        return new HttpRequestHandlerAdapter();
    }


    /* viewResolver() : View 코드로 지정된 주소에 위치한 jsp 파일의 경로를 사용하도록 설정
       (MvcConfig.configureViewResolvers()와 동일) */
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver vr = new InternalResourceViewResolver();
        /* setPrefix() : view 코드로 사용할 파일이 저장된 디렉토리 경로 */
        vr.setPrefix("/WEB-INF/view/");
        
        /* setSuffix() : view 코드로 사용할 파일의 확장자 */
        vr.setSuffix(".jsp");
        return vr;
    }
}

6. 요약 정리

  • DispatcherServlet
    : 웹 브라우저의 요청을 받는 창구 역할 및 요청 흐름 제어 역할 수행
  • HandlerMapping
    : 클라이언트의 요청을 실제로 처리하고 View 정보와 Model을 설정할 Handler 객체를 검색
  • HandlerAdapter
    : DispatcherServletHandler 객체 사이를 중계하면서 웹 클라이언트의 요청을 받아 그 처리 결과를 ModelAndView 객체로 알맞게 변환
  • ViewResolver : 요청 처리 결과를 생성할 View 검색
  • View : 최종 응답 결과 생성 후 클라이언트에게 전송

Reference

  • 초보 웹 개발자를 위한 스프링5 프로그래밍 입문(최범균)

0개의 댓글