Spring MVC을 알아보기 전에 ServletContainer에 대해서 먼저 알아보겠습니다.


ServletContainer란?

개발자가 웹서버 애플리케이션을 제작함에 있어서, 통신을 목적으로 소켓을 생성하고, port에 리스닝하는 등의 비즈니스 로직과 무관한 일을 처리해줍니다. 컨테이너는 servlet의 Life Cycle를 관리합니다. ServletContainer는 요청이 들어올 때마다 새로운 자바 스레드를 만듭니다. 이것이 동시에 여러 요청을 처리할 수 있게 되는 기반이 됩니다.

Springboot에서는 web 디펜더시 를 추가하면 ServletContainer로 Tomcat이 들어오게 됩니다. Tomcat 같은 WAS는 java 파일을 컴파일해서 Servlet 객체를 만듭니다.


Servlet 동작 과정

1. Client가 요청을 보낼시 HTTP Request가 ServletContainer에 들어옴

2. ServletContainer는 HttpServletRequest, HttpServeltResposen 객체를 생성

3. URL을 분석하여 담당하는 서블릿을 확인

4. service() 메소드를 호출하여 doGet() or doPost() 메서드를 호출

5. HttpServletResponse 객체에 응답을 실어 전송

6. 요청 완료시 HttpServletRequest, HttpServletResponse 객체 소멸

 

init(): 서버가 시작될 때 호출하여 서블릿이 생성됩니다.(애플리케이션이 시작될 때) (단 한 번 실행)

service(): 모든 유저들의 요청을 처리합니다.

destory(): 서버가 종료될 때 (애플리케이션이 종료될 때)


우리가 사용할 서블릿은 DispatcherServlet입니다.

DispatcherServlet은 ServletContainer에 의해서 생명주기가 관리됩니다. 하지만 DispatcherServlet은 SpringContainer에 존재하기 때문에 ServlerContainer는 DispatcherServlet의 존재를 알지못하는 상황이 생깁니다. 

따라서  Web.xml을 통해서 DispatcherServlet을 주입 하는 과정을 거칩니다. 


DispatcherServlet 상속

Servlet

public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();

    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    public String getServletInfo();

    public void destroy();
}

서블릿을 활용한 애플리케이션을 개발할 때 구현에 필요한 메소들을 가지고 있습니다. 이것을 구현해야 서블릿으로서의 역할을 할 수 있습니다.

GenericServlet

public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {

    private static final long serialVersionUID = 1L;

    private transient ServletConfig config;

    public GenericServlet() {
        // NOOP
    }

    @Override
    public void destroy() {
        // NOOP by default
    }

    @Override
    public String getInitParameter(String name) {
        return getServletConfig().getInitParameter(name);
    }

    @Override
    public Enumeration<String> getInitParameterNames() {
        return getServletConfig().getInitParameterNames();
    }

    @Override
    public ServletConfig getServletConfig() {
        return config;
    }

    @Override
    public ServletContext getServletContext() {
        return getServletConfig().getServletContext();
    }

    @Override
    public String getServletInfo() {
        return "";
    }

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

    public void init() throws ServletException {
        // NOOP by default
    }

    public void log(String message) {
        getServletContext().log(getServletName() + ": " + message);
    }

    public void log(String message, Throwable t) {
        getServletContext().log(getServletName() + ": " + message, t);
    }

    @Override
    public abstract void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    @Override
    public String getServletName() {
        return config.getServletName();
    }
}

Servlet을 상속하여 Clinet-Server 환경에서의 필요한 기능을 구현한 추상 클래스입니다.

HttpServlet

GenericServlet을 상속받고, HTTP 프로토콜 요청을 진행할 수 있게 구현돼있습니다.

public abstract class HttpServlet extends GenericServlet {

    private static final long serialVersionUID = 1L;

    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";

    private static final String HEADER_IFMODSINCE = "If-Modified-Since";
    private static final String HEADER_LASTMOD = "Last-Modified";

    private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
    private static final ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
	
    //중략
}

DispatcherServlet

가장 앞단에서 HTTP 프로토콜로 들어오는 요청을 적합한 컨트롤러에게 위임해주는 Front Controller Pattern에 사용되는 주체입니다. 

위에서 설명한 요청이 들어왔을 때 service()의 역할을 하는 doService()를 보겠습니다.

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
   logRequest(request);

   // 요청 종류를 포함한다면 map에 attribute를 넣습니다.
   Map<String, Object> attributesSnapshot = null;
   if (WebUtils.isIncludeRequest(request)) {
      attributesSnapshot = new HashMap<>();
      Enumeration<?> attrNames = request.getAttributeNames();
      while (attrNames.hasMoreElements()) {
         String attrName = (String) attrNames.nextElement();
         if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
            attributesSnapshot.put(attrName, request.getAttribute(attrName));
         }
      }
   }
   // WebApplicationContest, AnnotationConfigWebApplicationContext, ServletConfigClasses들이 바인딩 됩니다.
   request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
   request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
   request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
   request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

   // redirection을 사용할 때 임시적으로 담는 map입니다. 
   if (this.flashMapManager != null) {
      FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
      if (inputFlashMap != null) {
         request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
      }
      request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
      request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
   }

   RequestPath previousRequestPath = null;
   if (this.parseRequestPath) {
      previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
      ServletRequestPathUtils.parseAndCache(request);
   }

   try {
      // 요청을 실질적으로 처리하기 위한 로직
      doDispatch(request, response);
   }
   finally {
      if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
         if (attributesSnapshot != null) {
            restoreAttributesAfterInclude(request, attributesSnapshot);
         }
      }
      if (this.parseRequestPath) {
         ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
      }
   }
}
주석으로 약간의 설명을 달아놨습니다.

지금까지 ServletContainer를 알아봤습니다. 다음은 Spring MVC에 대해 자세히 알아보도록 하겠습니다. 

(잘못된 부분이 있어 지적해주시면 감사하겠습니다.)