Study/Spring

[스프링 웹 MVC 동작원리] Servlet과 DispatcherServlet의 동작원리

going.yoon 2022. 3. 1. 12:26

I. Servelet 소개

1. Servlet 이란?

- 자바 엔터프라이즈 에디션에서 웹 애플리케이션 개발을 위한 스펙과 API를 제공하는데, 그 중 가장 중요한 것이 HttpServlet이다.

- 요청마다 프로세스를 생성하는 것이 아니라 한 프로세스 내에 자원을 공유하는 쓰레드로 요청을 처리함.

  > 이식성이 좋다.

 

2. Servlet 엔진 또는 서블릿 컨테이너(톰캣, 제티 등)

- 세션관리

- 네트워크 서비스

- MIME 기반 메세지 인코딩, 디코등

- 서블릿 생명주기 관리

 

3. Servlet의 생명주기

 - init: 서블릿 컨테이너가 서블릿 인스턴스를 초기화

 - service: 서블릿 초기화 이후에는 모든 요청이 쓰레드 단위로 처리되고, 인스턴스의 service()메소드가 호출된다.

                 :이 안에서 HTTP 요청을 받고 클라이언트로 보낼 HTTP 응답을 만든다.

                 : service()는 보통 HTTP Method에 따라 doGet(), doPost() 등으로 처리를 위임한다.

                 : 따라서 보통 doGet()이나 doPost()를 구현한다.

- destroy : 서블릿 컨테이너 판단에 따라 서블릿을 메모리에서 내려야 할 때 destroy를 호출한다.

 

4. Servlet 리스너와 필터

  • 리스너 :  웹 애플리케이션에서 발생하는 이벤트 리스너

        * 이벤트 종류

         - 서블릿 컨텍스트 수준의 이벤트

                 - 컨텍스트 라이프사이클 이벤트

                 - 컨텍스트 애트리뷰트 변경 이벤트

         - 세션 수준의 이벤트

                 - 세션 라이프사이클 이벤트

                 - 세션 애트리뷰트 변경 이벤트

 

  • 필터 : 서블릿 컨테이너와 클라이언트간의 요청-응답 사이에 처리 로직을 넣을 수 있는 기능. 체인 형태의 구조이다.

서블릿 필터 이미지

 

 

5. DispatcherServlet

- 서블릿 애플리케이션에 스프링 연동하는 방법은 두가지가 있는데,

   - 서블릿에서 스프링이 제공하는 IoC 컨테이너 활용하는 방법

   - 스프링이 제공하는 서블릿 구현체 DispatcherServlet 사용하는 방법이 있다.

 

DispatcherServlet은 FrontController의 역할을 한다. 

 

 

Dispatcher servlet의 동작원리를 코드를 통해 알아보자.

컨트롤러에서 리턴값을 줄 때 뷰 모델을 리턴해줄 수 있고, 화면에 렌더링할 데이터를 리턴해 줄 수 도 있다.

// HelloController.class
@Controller
public class HelloController {

    @Autowired
    HelloService helloService;

    @GetMapping("/hello") // #1. 화면에 뿌려줄 데이터를 return 
    @ResponseBody // #1.1 이런 경우 responseBody 애노테이션을 붙여주어야 한다.
    public String hello(){
        return "Hello, " + helloService.getName();
    }

    @GetMapping("/sample") // #2. 뷰 모델을 return
    public String sample(){
        return "/WEB-INF/sample.jsp"; // #2.1 String만 return을 해줬는데 뷰의 이름으로 인식을 한다.
    }
}


// HelloService.class
@Service
@Getter
@Setter
public class HelloService {

    private String name;

}

 

#2.1 내용을 다시 보면 Controller에서 @ResponseBody 애노테이션 없이 return 값을 String만 줬을 때 스프링에서는 이를 뷰의 이름으로 인식한다는 것을 알 수 있다. 

Debug모드로 실행했을 때 ModelAndView객체에 맵핑되어 있음.

 

package com.going.spring;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@org.springframework.stereotype.Controller("/simple")
public class SimpleController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response){
        return new ModelAndView("/WEB-INF/simple.jsp")
    }
    
}

혹은 이런식으로 ModelAndView를 직접 지정해 줄 수도 있다.

 

다시한번 DispatcherServelet의 동작 순서를 확인해보자면

  • 요청을 분석한다(로케일, 테마, 멀티파트 등)
  • (핸들러 맵핑에서 위임하여) 요청할 핸들러를 찾는다.
  • (등록되어 있는 핸들러 어댑터 중에) 해당 핸들러를 실행할 수 있는 "핸들러 어댑터"를 찾는다.
  • 찾아낸 "핸들러 어댑터"를 사용해서 핸들러의 응답을 처리한다.
  • 핸들러의 리턴값을 보고 어떻게 처리할지 판단한다. 
     1. 뷰 이름에 해당하는 뷰를 찾아서 모델 데이터를 렌더링한다.
     2.@ResponseBody가 있다면 Converter를 사용해서 응답 본문을 만들고, 최종적으로 응답을 보낸다.

 

 

 

 

 

 

 

DispatcherServlet의 동작원리를 조금 더 자세하게 알아보기 위해 아래의 경로에 있는 DispatcherServlet.java의 소스를 열어보자.

package org.springframework.web.servlet;

 

DispatcherServlet이 호출되면 가장먼저 실행되는 초기화 전략함수가 아래의 순서에 따라 호출되고,

각각의 함수는 각각의 전략에 따라 서브클래스에서 override될 수 있다.

/**
 * 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);
}

 

그 중에서도 initViewResolvers전략을 살펴보면 아래와 같다. 먼저 ApplicationContext에서 ViewResolver로 등록된 빈들을 가져오고, 등록된 빈이 없으면 기본 전략을 사용한다.

/**
 * Initialize the ViewResolvers used by this class.
 * <p>If no ViewResolver beans are defined in the BeanFactory for this
 * namespace, we default to InternalResourceViewResolver.
 */
private void initViewResolvers(ApplicationContext context) {
   this.viewResolvers = null;

   if (this.detectAllViewResolvers) {
      // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
      Map<String, ViewResolver> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
      if (!matchingBeans.isEmpty()) {
         this.viewResolvers = new ArrayList<>(matchingBeans.values());
         // We keep ViewResolvers in sorted order.
         AnnotationAwareOrderComparator.sort(this.viewResolvers);
      }
   }
   else {
      try {
         ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
         this.viewResolvers = Collections.singletonList(vr);
      }
      catch (NoSuchBeanDefinitionException ex) {
         // Ignore, we'll add a default ViewResolver later.
      }
   }

   // Ensure we have at least one ViewResolver, by registering
   // a default ViewResolver if no other resolvers are found.
   if (this.viewResolvers == null) {
      this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
      if (logger.isTraceEnabled()) {
         logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
               "': using default strategies from DispatcherServlet.properties");
      }
   

 

아래와 같이 Subclass에서 Override 하여 사용도 가능하다.

@Configuration
@ComponentScan
public class WebConfig {

    @Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}

 

 

 

 

 

 

그 외 다른 전략들도 DispatcherServlet.properties의 소스를 통해 살펴보자.

 

1. MultipartResolver

 - 파일 업로드 요청 처리에 필요한 인터페이스

 - MultipartResolver 빈이 구현이 되어있어야 사용이 가능하다.

 - HttpServletRequest를 MultipartHttpServletRequest로 변환해주어 요청이 담고 있는 File을 꺼낼 수 있는 API 제공

 

2. LocaleResolver

 - 클라이언트의 위치(Locale) 정보를 파악하는 인터페이스

 - 기본 전략은 요청의 accept-language를 보고 판단.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

 

3. ThemeResolver

 - 애플리케이션에 설정된 테마를 파악하고 변경할 수 있는 인터페이스

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

 

4. HandlerMapping

 - 요청을 처리할 핸들러를 찾는 인터페이스

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
   org.springframework.web.servlet.function.support.RouterFunctionMapping

 - 메소드 자체를 핸들러로 설정할 때는 애노테이션 기반(RequestMappingHandlerMapping)으로 / 클래스 자체를 핸들러로 설정할 때는 (BeanNameUrlHandlerMapping)을 기반으로 사용한다.

 

5. HandlerAdaptor

- HandlerMapping이 찾아낸 핸들러를 처리하는 인터페이스

- 스프링 MVC 확장력의 핵심

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
   org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
   org.springframework.web.servlet.function.support.HandlerFunctionAdapter

 - @Controller 애노테이션 아래 구현된 함수라고 보면 된다. 사용자가 언제든지 Handler의 로직을 구현할 수 있기 때문에 확장력의 핵심이 된다.

 

6. HandlerExceptionHandler

- 요청 처리 중에 발생한 에러 처리하는 인터페이스

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
   org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
   org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

 

7. RequestToViewNameTranslator

- 핸들러에서 뷰 이름을 명시적으로 리턴하지 않은 경우, 요청을 기반으로 뷰 이름을 판단하는 인터페이스

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

 

8.ViewResolver

- 뷰 이름(String)에 해당하는 뷰를 찾아내는 인터페이스

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

 

9. FlashMapManager

- FlashMap 인스턴스를 가져오고 저장하는 인터페이스

- FlashMap은 주로 리다이렉션을 사용할 때 요청 매개변수를 사용하지 않고 데이터를 전달하고 정리할 때 사용한다.

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager