Project/Sell-everything

[#3] 그래서 DispatcherServlet이 대체 뭐하는 녀석이죠?

개발하자 2021. 9. 14. 13:25

스프링 MVC를 통해 처음 스프링을 접하면서, 굉장히 혼란스러웠던 기억이 납니다.

 

의존성 주입, AOP, PSA, Application Context 등 스프링을 구성하는

 

방대한 용어들이 쏟아지니, 초심자 입장에서 쉽지 않았던 경험이었네요.

 

 

그중에서도, 유독 이해가 되지 않던 것이 DispatcherServlet이란 친구였습니다.

 

DispatcherServlet의 작동원리니, FrontController니 하는 일련의 개념들에 대해서는 각각 이해할 수 있어도,

 

그래서 DispatcherServlet이 왜 존재하는데?라는 의문은 항상 남아있었습니다.

 

최근에 Servlet을 자세히 공부하면서, 왜 스프링 MVC가 DispatcherServlet을 사용하는지

 

이해하게 되었고, 그 이유를 아래와 같은 짧은 문장으로 얘기할 수 있을 것 같습니다.

 

개발자에게 서블릿 컨텍스트 구현의 책임이 아닌,
스프링 컨텍스트 구현의 책임을 주는 것

 

 

하지만 이렇게만 이야기하면 추상적인 느낌이 강할 수 있기 때문에,

 

조금 더 구체적으로 서블릿을 탐구하면서 위의 문장을 발견해보도록 하겠습니다.

 

함께 가시죠!

 

 


목차

  1. Servlet?
  2. GenericServlet과 HttpServlet
  3. HttpServlet의 동작 과정
  4. Servlet Container와 Spring Container를 연결하는 Dispatcher Servlet
  5. DispatcherServlet은 Servlet Container와 Spring Container를 어떻게 연결할까?

Servlet?

서블릿은 JavaEE에 종속된 기술로, JavaEE docs에서 그 정의를 살펴보면 다음과 같습니다.

A servlet is a Java programming language class that is used to extend the capabilities of servers that host applications accessed by means of a request-response programming model. Although servlets can respond to any type of request, they are commonly used to extend the applications hosted by web servers.

서블릿은 요청-응답 프로그래밍 모델을 통해 액세스 되는 응용 프로그램을 호스팅 하는 서버의 기능을 확장하는 데 사용되는 Java 프로그래밍 언어 클래스입니다. 서블릿은 모든 유형의 요청에 응답할 수 있지만 일반적으로 웹 서버에서 호스팅 하는 애플리케이션을 확장하는 데 사용됩니다.

 

서버의 기능을 확장한다는 말이 조금 난해한데, 서블릿을 사용하는 패턴을 기반으로 추측해보면 서버에서 다양한 요청을 처리하는 것에는 굉장히 많은 로우 레벨 기술을 필요로 하는데, 그러한 일련의 기술을 서블릿이 제공하는 기능을 통해 선언적인 방식으로 구현할 수 있다는 말로 이해했습니다. JavaEE의 기술적인 목표가 로우 레벨 기술을 개발자 대신 구현해서 더 쉽게 기술을 사용할 수 있게 하는 것이었으니, 충분히 이러한 관점으로 이해할 수 있습니다.

 

서블릿은 모든 타입의 요청에 응답할 수 있지만, 일반적으로 웹 서버에서 호스팅 하는 애플리케이션을 확장하는 데 사용된다고 합니다. 그래서 많은 사람들이 서블릿을 좁은 의미로 설명할 때, 자바 기반의 동적 웹 페이지를 제공하기 위한 기술이라고 설명하기도 합니다.

GenericServlet과 HttpServlet

조금 더 구체적으로 서블릿을 어떻게 사용하는지 알아보겠습니다.

 

서블릿을 작성하기 위해서, javax.servlet과 javax.servlet.http에서 제공하는 클래스나 인터페이스를 상속해야 합니다.

 

HTTP 기반이 아닌 일반 서비스 구현을 위해서는 GenericServlet 클래스를 확장하고,
HTTP 기반의 서비스를 구현하기 위해서는 GenericServlet을 확장한 HttpServlet 클래스를 확장합니다.

 

GenericServlet은 서블릿의 생명주기 관리 및 기능에 대한 코드를 작성하도록 하고, HttpServlet은 일반적인 서블릿의 기능과 HTTP와 관련한 메서드, 이를테면 doGet, doPost의 메서드 등을 추가적으로 제공합니다.

 

아래의 구체적인 서블릿 클래스 코드를 보시면, 어떠한 방식으로 서블릿을 작성해야 하는지 감이 잡힐 것이라 생각합니다.

/* GenericServlet.java */
/* 주로 서블릿의 생명주기 관리 메서드를 작성 */
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {

    ...
    public void destroy() {
    }
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
/* HttpServlet.java */
/* Http 관련 기능을 확장한 Servlet */
public abstract class HttpServlet extends GenericServlet {
    ...
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_get_not_supported");
        this.sendMethodNotAllowed(req, resp, msg);
    }
    ...
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_post_not_supported");
        this.sendMethodNotAllowed(req, resp, msg);
    }

    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_put_not_supported");
        this.sendMethodNotAllowed(req, resp, msg);
    }
    ...
}

HttpServlet의 동작 과정

HttpServlet 동작 과정

서블릿을 통해, 웹 서버가 클라이언트 요청을 처리하는 과정을 요약하면 다음과 같습니다.

 

  1. 클라이언트로부터 HTTP Request 전송
  2. 서블릿 컨테이너(ex. Tomcat)는 요청을 전송받아, HttpServletRequest, HttpServletResponse 객체 생성
  3. web.xml에 정의된 url pattern을 확인하고, 요청을 처리할 서블릿 검색 or 생성
  4. 서블릿 컨테이너는 요청을 처리할 Thread 객체를 생성하고, 해당 서블릿 객체의 service() 메서드 호출
  5. service 메서드 호출 후, Http 요청 메서드에 따라 doGet(), doPost() 등의 메서드 호출
  6. 요청 처리가 완료되면, HttpServletRequest 및 HttpServletResponse 객체 소멸

요약하면, HTTP 요청마다 요청을 처리할 서블릿을 생성하고, 생성한 서블릿을 요청에 맵핑하여 HTTP Request를 처리하는 Actor로 사용합니다.

 

여기까지가 서블릿을 통해 Http 요청을 처리하던 방식입니다. 하지만, 위의 패턴은 서블릿을 상속하는 방식이기 때문에, 개발자의 코드를 서블릿이라는 기술에 종속되게 한다는 큰 문제점이 있습니다. JavaEE가 사장되어 현재는 거의 사용하지 않는 이유도, 코드의 기술 결합도를 너무 높여서 재사용성을 떨어뜨린다는 것이었는데, 서블릿에서도 그 모습을 확인할 수 있었습니다.

 

스프링은 이러한 문제점을, 컨테이너 계층을 Servlet Container - Spring Container로 분리하고 DispatcherServlet을 통해 연결하여 해결합니다.

Servlet Container와 Spring Container를 연결하는 DispatcherServlet

스프링 MVC 서버 구성도

 

앞서 말했듯, Servlet Container는 Http 요청과 Servlet을 맵핑하여 처리합니다. 그래서 JavaEE 등의 레거시에서는 Servlet 추상 클래스의 구현체를 만들어 사용했으나, 스프링 MVC에서는 DispatcherServlet이라는 하나의 서블릿을 통해 모든 요청을 수신하고, 적당한 Controller Layer(ex. Controller- Service - DAO)를 검색하여 비즈니스 로직을 처리한 후 결과를 반환하는 형태로 요청을 처리합니다. 이러한 방식을, FrontController 패턴이라고 부릅니다.

 

FrontController

스프링 MVC의 DispatcherServlet이 Servlet 구현의 책임을 가져가고, 개발자에게는 서블릿이 아닌 스프링 컨텍스트 작성의 책임을 주었기 때문에, 기존 JavaEE에 종속된 프로그램을 만들어야 했던 패턴을 벗어나 지금의 POJO 지향 프로그램 개발이 가능했다고 생각되네요!

DispatcherServlet은 Servlet Container와 Spring Container를 어떻게 연결할까?

그럼, DispatcherServlet이 Servlet Container와 Spring Container를 어떻게 연결하는지 궁금해집니다.

 

DispatcherServlet은 Servlet Container에 포함된 개념이기 때문에, DispatcherServlet에서 Spring Container를 참조할 수 있기만 한다면, 두 컨테이너의 연결이 성립한다고 볼 수 있겠습니다. Spring Container란, 사실상 ApplicationContext를 의미한다고 볼 수 있죠. 다시 말해, Spring Container를 참조할 수 있다는 것은 ApplicationContext를 알고 있다는 것이고, 이러한 맥락에서 클래스에 ApplicationContext를 주입하는 인터페이스인 ApplicationContextAware를 떠올릴 수 있습니다.

 

코드 수준에서 이를 살펴보도록 하겠습니다.

 

/* 생성자 주입을 통해 ApplicationContext를 주입 */
public class DispatcherServlet extends FrameworkServlet {
    ...
    public DispatcherServlet(WebApplicationContext webApplicationContext) {
        super(webApplicationContext);
        this.setDispatchOptionsRequest(true);
    }
    ...
}

/* ApplicaionContextAware를 구현하여 런타임에 ApplicationContext 의존성 주입이 일어남. */
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...
}

DispatcherServlet의 계층구조를 보면, DispatcherServlet이 ApplicationContextAware를 구현하고 있고, 생성자 주입을 통해 WebApplicationContext를 주입받는 것을 알 수 있습니다.

DispatcherServlet 계층 구조

위와 같은 방식으로, Servlet Container와 Spring Container는 연결되어 작동합니다.

 


마치며

 

DispatcherServlet이 어떠한 방식으로 작동하여, 기존 서블릿의 문제점을 해결하는지 살펴보았습니다.

 

스프링이 기존 엔터프라이즈 시스템 개발 패턴의 문제를 해결해나가는 과정들을 살펴보다 보면,

 

하나같이 '객체지향' 설계를 기반으로 문제를 풀어나간다는 공통점을 발견할 수 있었습니다.

 

스프링이 '서버 프레임워크'가 아니라, '객체지향 프레임워크'임을 느끼게 되는 순간이

 

스프링을 더 잘 사용할 수 있게 되는 때가 아닐까 생각되네요 :)

 

 

프로젝트 URL : https://github.com/f-lab-edu/sell-everything

 

GitHub - f-lab-edu/sell-everything: Spring Framework 기반 중고 거래 서비스 플랫폼

Spring Framework 기반 중고 거래 서비스 플랫폼. Contribute to f-lab-edu/sell-everything development by creating an account on GitHub.

github.com

 

반응형