서론
스프링에 관한 면접 질문에 대한 답을 정리하고 공부하는 과정에서 Sevlet과 DispatcherServlet, Servlet Container 등의 용어를 발견했지만 이에 대한 개념이 정확히 무엇인지 의문이 생겨 한 번 정리하며 제대로 공부해보기 위해서 글로 정리했다!
Servlet이란?
웹서버가 동적인 페이지를 제공할 수 있도록 도와주는 자바 프로그램
웹서버는 일반적으로 정적인 페이지만을 제공한다.
따라서 동적인 페이지를 제공하기 위해서 그 역할을 담당하는 것이 Servlet
Servlet의 라이프 사이클
- 최초 호출시
init()
메소드로 생성 - 요청이 들어오면
service()
메소드를 호출하고, 요청의 HTTP method에 따라doGet()
,doPost()
등의 메소드를 호출하여 요청을 처리한다 - Servlet Container를 종료하는 등 사용을 마치면
destroy()
메소드로 파괴한다.
HttpServlet
추상 클래스의 service()
메소드
package jakarta.servlet.http;
public abstract class HttpServlet extends GenericServlet {
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
...
doGet(req, resp);
...
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req, resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req, resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
...
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
}
service()
메소드 내부에서 요청의 HTTP method에 따라 적절한 메소드를 호출하는 모습을 볼 수 있다.
HTTP의 Servlet이기 때문에 service()
메소드 내에서 doGet()
, doPost()
와 같은 메소드를 호출하며, 만약 다른 프로토콜에 관련된 Servlet이라면 메소드 내에서 프로토콜 규격에 맞게 다른 메소드를 호출할 것이라고 예측된다.
Servlet Container
코드로 작성한 서블릿은 알아서 작동하는 것이 아니기 때문에, 이를 관리해줄 주체가 필요하다.
바로 그 역할을 Servlet Container가 담당한다.
간단하게 조사한 바로는 web.xml
파일에 생성한 Servlet 클래스를 등록하고 url 패턴을 매핑하는 방식으로 정보를 제공해, 이를 기반으로 Servlet Container가 요청에 따른 적절한 Servlet을 찾는다고 한다.
클라이언트에게 요청이 들어왔을 때 매핑 정보를 보고 요청 URL에 적절한 Servlet을 찾아 전달해 service()
메소드를 호출한다.
이 때 필요한 Servlet이 초기화되어있지 않다면 init()
메소드를 호출해서 생성한 후 요청 처리를 진행한다.
대표적인 Servlet Container로는 Tomcat이 있다
그렇지만 Spring Boot로 웹 서버를 개발하면서 Servlet 클래스를 따로 만들지도, 매핑을 하지도 않았다. 각 URL에 매핑을 하는 일은 Container 클래스를 생성한 기억밖에 없다…
그럼 Spring Boot에선 Servlet은 어떻게 활용되는 것일까?
DispatcherServlet
Spring MVC의 요청 처리 과정에 대해 공부하다보면 바로 DispatcherServlet이라는 용어를 볼 수 있다.
Spring MVC는 DispatcherServlet이라는 하나의 Servlet만 생성해두고 개발자가 별도의 Servlet 구현 없이 컨트롤러에 동적 처리를 위임하는 구조를 갖는다.
이 구조를 프론트 컨트롤러 패턴이라고 한다.
DispatcherServlet
클래스 코드 일부
package org.springframework.web.servlet;
public class DispatcherServlet extends FrameworkServlet {
...
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
}
DispatcherServlet
클래스는 org.springframework.web.servlet.FrameworkServlet
추상 클래스를 상속하고 있으며, 이 추상 클래스는 위의 HttpServlet
추상 클래스를 상속하고 있다.
initStrategies()
메소드를 보면 위 그림에서 표현되어있는 HandlerMapping
, HandlerAdapters
, ViewResolver
등의 이름이 보이는 것을 알 수 있다.
총정리
가장 궁금했던 부분은 “내가 만들던 스프링 프로젝트에서 Servlet들은 어떻게 되어있는 걸까?” 였다.
간단히 정리하자면 Spring에서는 DispatcherServlet이라는 Servlet 하나만을 만들어두는 듯 하다. 그리고 개발자는 여러 개의 Controller 클래스를 생성하고 URL 패턴을 매핑하여, 어플리케이션을 실행할 때는 요청이 들어오면 URL을 보고 적절한 Controller로 요청을 전달하는 구조로 동적 처리를 진행한다.
웹 서버 어플리케이션을 실행하고 처음 요청하면 DispatcherServlet을 초기화하는 로그를 발견할 수 있다.
이후 다른 요청이 들어오면 이미 DispatcherServlet은 초기화되어있기 때문에, 같은 로그가 두 번 발생하진 않는다.