Java 백엔드 개발자는 대부분 Spring Framework를 사용한다. 그중에서도 Spring MVC, Spring Boot를 사용해 개발하는데, 이 모든 프레임워크의 핵심에는 공통된 기반 기술인 Servlet이 존재한다.
서블릿은 HTTP 요청-응답 흐름을 처리하기 위한 자바 표준 인터페이스이다.
Spring MVC의@Controller
,@RestController
도 결국 내부적으로는 DispatcherServlet이라는 서블릿 위에서 동작한다.
왜 알아야 할까?
서블릿: Java를 사용해 동적인 웹 페이지를 생성할 수 있도록 해주는 서버 사이드 웹 프로그래밍 기술
예를 들면, 사용자가 로그인 시 아이디/비밀번호를 입력해 서버로 전송하면 서블릿은 해당 요청을 받아 DB와 비교하여 인증 처리를 수행한다. 그에 따른 적절한 응답을 클라이언트에 전달한다.
핵심 기능과 특징
서블릿은 자바 클래스로 작성되며, 서블릿 컨테이너(ex. 톰캣)에 의해 관리한다.
컨테이너는 서블릿의 생성부터 요청 처리, 종료까지의 전체 과정을 자동으로 관리한다. 이를 서블릿의 생명주기라고 부른다.
서블릿 생명주기 메서드
생명주기 흐름
init()
메서드를 호출하여 한 번만 초기화 작업을 수행한다.init()
은 서블릿당 1번만 호출된다.load-on-startup
설정에 따라 초기화 시점이 달라진다.. load-on-startup
값을 설정하면 웹 애플리케이션이 시작될 때 즉시 초기화init()
이후 클라이언트의 요청이 들어올 때마다 service()
메서드가 실행된다.service()
메서드는 HTTP 요청 방식(GET, POST 등)에 따라 doGet() or doPost()로 나뉜다.destroy()
메서드가 호출된다.destroy()
도 한 번만 호출되며, 리소스 정리나 로그 저장 등 종료 작업을 수행할 수 있다.서블릿은 기본적으로 싱글톤으로 관리된다.
따라서 init()
, destroy()
는 한 번씩만 호출되고doGet()
, doPost()
는 매 요청마다 호출된다.
요청마다 생성되는 HttpServletRequest, HttpServlerResponse 객체는 요청 처리 후 컨테이너에 의해 자동으로 정리된다.
service()
메서드를 호출하고, HTTP 메서드 (GET, POST 등)에 따라 doGet(), doPost() 메서드로 분기된다.요약
사용자의 HTTP 요청은 서블릿 컨테이너에 의해
URL 메핑 -> 서블릿 실행 -> 응답 처리
의 흐름으로 이어지며, 이 과정은 요청마다 새로운 HttpServletRequest/HttpServletResponse 객체가 생성되어 처리된다.
실제 내부 동작 기반 설명
GET http://localhost:8080/login
//서블릿 객체가 없다면 newInstance()
Servlet servlet = new LoginServlet();
//init() 호출
servlet.init()
servlet.service(request, response);
-------
// service 메서드 내부 동작
public void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod(); // GET or POST
if (method.equals("GET")) {
doGet(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
}
}
doGet()
또는 doPost()
에서는 요청에 따른 비즈니스 로직을 수행하고, response.getWriter().write(...)
등을 통해 HTML, JSON 등의 응답 데이터를 작성한다.전체 흐름 요약
[브라우저]
↓ 요청
[Coyote HTTP Connector]
↓
[Engine → Host → Context]
↓
[URL 매핑: Wrapper 선택 (Servlet 매핑)]
↓
[서블릿 service() 실행]
↓
[doGet()/doPost() → 응답 생성]
↓
[Response 전송 → 객체 정리]
Spring MVC는 단순히 @Controller
를 사용하는 추상화된 프레임워크처럼 보이지만, 핵심에는 DispatcherServlet이 존재한다.
Spring은 web.xml
혹은 Java Config를 통해 DispatcherServlet을 서블릿으로 등록하고, 이 서블릿이 모든 HTTP 요청을 받아서
핸들러 매핑 -> 인터셉터 -> 컨트롤러 -> 뷰 처리
로 이어지는 전체 요청 흐름을 담당한다.
Spring MVC에서 클라이언트의 요청을 실제로 받아 처리하는 핵심 컴포넌트는 바로 DispatcherServlet이다.
DispatcherServlet은 Spring이 제공하는 특수한 서블릿으로, 모든 요청을 중앙에서 받아 분기하고 흐름을 제어하는 역할을 한다.
DispatcherServlet = 요청의 진입점이자 전체 흐름의 조율자
DispatcherServelt의 동작 흐름
[요청]
↓
DispatcherServlet
↓
HandlerMapping (어떤 Controller인지 찾기)
↓
HandlerAdapter (어떻게 호출할지 결정)
↓
Controller (@Controller 클래스의 메서드 실행)
↓
ModelAndView 생성
↓
ViewResolver (어떤 뷰를 렌더링할지 찾기)
↓
View (JSP, Thymeleaf 등 렌더링)
↓
[응답]
서블릿과 DispatcherServlet 비교
구분 | 서블릿 (Servlet) | DispatcherServlet (Spring MVC) |
---|---|---|
요청 수신 | 서블릿 컨테이너가 직접 요청 처리 | 서블릿 컨테이너는 DispatcherServlet에 요청 전달 |
매핑 방식 | web.xml 또는 @WebServlet | web.xml , @Configuration , WebApplicationInitializer 등 |
요청 처리 | service() → doGet() / doPost() | doDispatch() → Controller 메서드 호출 |
파라미터 처리 | request.getParameter() 직접 호출 | @RequestParam , @RequestBody 등으로 자동 바인딩 |
응답 처리 | response.getWriter().write(...) 직접 작성 | ModelAndView , ResponseEntity , ViewResolver 등으로 처리 |
확장성 | 구조가 단순하나 수동으로 구현해야 함 | 인터셉터, 예외 처리, 어댑터 등 다양한 컴포넌트로 유연하게 확장 |
DispatcherServlet 내부 동작
DispatcherServlet은 HttpServlet을 상속한 서블릿 그 자체이다.
/* DispatcherServlet */
public class DispatcherServlet extends FrameworkServlet {
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) {
doDispatch(request, response);
}
}
---
/* DoDispatch() 내부 작업 */
// 요청을 처리할 핸들러 찾기
HandlerExecutionChain handler = getHandler(request);
// 핸들러 어댑터 결정
HandlerAdapter adapter = getHandlerAdapter(handler.getHandler());
// 컨트롤러 메서드 호출
ModelAndView mv = adapter.handle(request, response, handler.getHandler());
// 뷰 이름 → ViewResolver 통해 View 객체로 변환
View view = resolveViewName(mv.getViewName());
// View 렌더링
view.render(mv.getModel(), request, response);
Spring은 서블릿의 기본 구조는 유지하면서, 그 위에 컴포넌트를 층으로 쌓아 추상화하고 확장한 구조이다.
DispatcherServlet 등록 방식: Spring Boot
@Configuration
public class WebConfig {
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
}
Spring Boot에서는 기본적으로 경로에 DispatcherServlet이 등록되어 있어서 모든 요청이 DispatcherServlet을 거쳐 들어오도록 설정되어 있다.
DispatcherServlet 역시 서블릿 컨테이너인 톰캣 위에서 동작하며, 톰캣은 요청을 수신하고 서블릿을 실행하는 기반 환경을 제공한다.
✅정리
DispatcherServlet은 서블릿 위에 구축된 Spring MVC 아키텍처의 중심축이다.
서블릿 생명주기와 매핑 구조를 그대로 따르면서,
요청 처리 흐름을 분리하고 확장할 수 있는 핵심 컴포넌트 체계를 제공한다.
지금까지 알아본 핵심 구조
이 흐름을 하나의 시퀀스로 통합해보자
전체 요청 처리 흐름
[사용자 (브라우저)]
↓ HTTP 요청 (GET /login)
[TCP/IP, 네트워크 스택]
↓
[서블릿 컨테이너 (톰캣)]
↓
[DispatcherServlet (Spring)]
↓
[HandlerMapping] → 해당 컨트롤러 찾기
↓
[HandlerAdapter] → 호출 방법 결정
↓
[@Controller 메서드 실행]
↓
[ModelAndView or @ResponseBody 응답 생성]
↓
[ViewResolver → View 렌더링]
↓
[HttpServletResponse에 응답 작성]
↓
[사용자에게 HTTP 응답 반환]
실제 코드 흐름
// DispatcherServlet → doDispatch()
HandlerExecutionChain handler = getHandler(request); // 요청을 처리할 핸들러 찾기
HandlerAdapter adapter = getHandlerAdapter(handler.getHandler()); // 핸들러 어댑터 호출
ModelAndView mv = adapter.handle(request, response, handler.getHandler()); // 실행
View view = resolveViewName(mv.getViewName()); // 뷰 찾기
view.render(mv.getModel(), request, response); // 렌더링
✅정리
지금까지 내용을 통해
HTTP 요청 -> DispatcherServlet -> 핸들러 실행 -> 응답 전송
에 이르는
전체 Spring MVC 요청 흐름을 구조적으로 이해했다.
Spring MVC는 매우 잘 추상화된 프레임워크다.
덕분에 @Controller
, @RestController
, @Component
같은 어노테이션만으로도 쉽게 웹 애플리케이션을 만들 수 있다.
하지만 편리함의 이면에는 복잡한 구조와 흐름이 존재하고, 그 중심에는 서블릿과 DispatcherServlet이 존재한다.
이번 정리를 통해 얻은 핵심 인사이트
- 서블릿은 자바 웹 기술의 시작점이자, 모든 웹 요청을 처리하는 표준 컴포넌트이다.
- DispatcherServlet은 Spring이 제공하는 서블릿 확장체로, 요청을 받고 처리 흐름을 조율하는 중심축이다.
- Spring의 모든 요청 처리 과정은 결국 서블릿 컨테이너 위에서 동작하며, 내부적으로는 서블릿 생명주기와 URL 매핑, HTTP 요청/응답 처리 로직이 녹아 있다.
출처
https://docs.oracle.com/javaee/6/tutorial/doc/bnafd.html
https://www.oracle.com/java/technologies/java-servlet-tec.html
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet.html