서블릿 컨테이너의 중요한 역할 중 하나는 서블릿 클래스의 인스턴스를 생성하고, 요청 URL과 서블릿 인스턴스를 매핑하며, 클라이언트 요청에 해당하는 서블릿을 찾아 작업을 위임하는 것이다.
이외에도 서블릿 컨테이너는 서블릿과 관련된 초기화(init)와 소멸(destroy) 작업까지 담당한다.
이 흐름을 이해하려면, 서블릿이 어떤 규약(인터페이스)을 기반으로 동작하는지부터 보는 것이 가장 깔끔하다.
Servlet 인터페이스로 보는 서블릿의 핵심 규약
서블릿은 본질적으로 Servlet 인터페이스 규약을 따른다. (보통은 HttpServlet을 상속하지만, 그 바닥에는 결국 Servlet 규약이 있다.)
package javax.servlet;
import java.io.IOException;
public interface Servlet {
void init(ServletConfig config) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
String getServletInfo();
void destroy();
}
여기서 핵심은 다음이다.
- init() : 서블릿 초기화 단계에서 컨테이너가 호출
- service() : 요청이 올 때마다 컨테이너가 호출 (HTTP라면 내부적으로 doGet/doPost로 분기됨)
- destroy() : 컨테이너가 종료될 때 컨테이너가 호출
즉, 개발자가 직접 호출하는 메서드가 아니라, 컨테이너가 정해진 타이밍에 호출해주는 메서드라는 점이 핵심이다.
서블릿 컨테이너 시작 시 동작 과정 (초기화 단계)
서블릿 컨테이너가 시작할 때 내부적으로 다음 과정을 거쳐 서블릿 초기화를 끝낸다.
- 클래스패스에서 Servlet 인터페이스를 구현한 서블릿 클래스 탐색
- @WebServlet 설정을 읽어 요청 URL ↔ 서블릿 매핑 등록
- 서블릿 인스턴스 생성
- init() 메서드 호출로 초기화 수행
이 과정을 통해 “이 URL 요청이 오면 이 서블릿을 실행한다”라는 준비가 완료된다.
요청이 들어오면 무엇이 일어날까? (서비스 단계)
서블릿 컨테이너는 위 과정으로 초기화를 끝낸 뒤에는 클라이언트 요청이 들어올 때까지 대기 상태로 있다가, 요청이 들어오면 다음처럼 처리한다.
- 요청 URL을 확인한다.
- URL에 매핑된 서블릿을 찾는다.
- 해당 서블릿의 service() 메서드를 호출한다.
(HTTP 기반이면 service() 내부에서 doGet(), doPost() 등으로 분기된다.)
즉, 요청 처리의 진입점은 컨테이너이며, 서블릿은 컨테이너가 선택해 호출하는 실행 대상이다.
서블릿 컨테이너 종료 시 동작 과정 (소멸 단계)
서비스를 수행하다가 서블릿 컨테이너를 종료하면, 컨테이너는 자신이 관리하던 모든 서블릿에 대해 다음을 수행한다.
- 각 서블릿의 destroy() 메서드 호출
- 소멸(정리) 작업 수행
여기서 말하는 소멸 작업은 예를 들어 이런 것들이다.
- 열어둔 리소스 정리(파일 핸들, 네트워크 연결 등)
- 캐시 정리
- 백그라운드 스레드 종료 등
이 전체 흐름이 “서블릿 생명주기”다
정리하면 서블릿은 다음 순서로 전체 과정을 거친다.
- 생성(Create)
- 초기화(Init)
- 서비스(Service)
- 소멸(Destroy)
이 전체 과정을 서블릿의 생명주기(Lifecycle) 라고 하고,
서블릿 컨테이너가 이 과정을 책임지기 때문에 “서블릿 컨테이너는 서블릿의 생명주기를 관리한다” 라고 말한다.
또한 컨테이너는 생명주기 관리 외에도 다음 기능들을 제공해 개발자가 비즈니스 로직에 집중하도록 돕는다.
- 멀티스레딩 지원
- 설정 파일 기반 보안 관리
- JSP 지원 등
“컨테이너”라는 개념은 왜 자주 등장할까?
자바 진영에서 웹 애플리케이션을 개발하다 보면 “컨테이너”라는 용어를 정말 자주 접한다.
각 컨테이너는 제공 기능이 조금씩 다를 수 있지만, 공통적으로 “객체의 생명주기를 관리한다” 는 성격을 갖는다.
예를 들어 스프링 프레임워크의 빈 컨테이너는 “빈(Bean)”의 생명주기를 관리한다.
- 빈을 생성하고
- 필요한 의존성을 주입하고
- 초기화 콜백을 호출하고
- 종료 시 소멸 콜백을 호출하는 식이다
이렇게 보면 서블릿 컨테이너와 스프링 컨테이너는 다루는 객체가 다를 뿐, “컨테이너가 객체를 생성하고 관리한다” 는 큰 구조는 비슷하다.
컨테이너가 객체를 관리한다는 건 무슨 의미일까?
컨테이너가 관리하는 객체 인스턴스는 개발자가 직접 new 해서 만드는 인스턴스가 아니다.
만약 개발자가 직접 인스턴스를 만든다면,
- 원하는 타이밍에 초기화 메서드 호출
- 종료 시점에 정리 메서드 호출
같은 작업을 개발자가 직접 하면 된다.
하지만 컨테이너가 인스턴스를 관리하면 개발자는 new를 하지 않고, 컨테이너가 알아서 생성·관리한다.
그래서 초기화/소멸 같은 작업을 할 수 있도록 인터페이스 규약(예: init/destroy) 을 만들어 두고,
컨테이너가 그 규약에 맞춰 타이밍을 보장해주는 구조가 된다.
이 관점으로 공부하면, 새로운 컨테이너를 만나도 학습 속도가 빨라진다.
“아, 얘도 결국 객체 생명주기를 관리하겠구나”라는 큰 그림이 먼저 잡히기 때문이다.
가장 중요한 질문: 서블릿 인스턴스는 몇 개 생성될까?
서블릿에서 반드시 알아야 할 중요한 포인트 중 하나가 바로 이거다.
서블릿 컨테이너는 멀티스레드로 동작한다.
동시에 여러 클라이언트 요청을 처리해야 한다.
그렇다면 서블릿 인스턴스는 몇 개가 생성될까?
스레드마다 새 인스턴스를 만들까?
결론부터 말하면 일반적인 서블릿 컨테이너 동작은 다음과 같다.
- 서블릿 인스턴스는 보통 1개만 생성된다.
- 그리고 여러 스레드가 그 “하나의 서블릿 인스턴스”를 공유해서 재사용한다.
이 구조는 이전에 HTTP 실습에서 만들었던 RequestMapping을 떠올리면 이해가 더 쉽다.
HTTP 실습에서 RequestMapping의 Map을 보면, 보통 static으로 만들어 서버 시작 시 한 번 초기화하고 계속 재사용한다.
서블릿 역시 비슷하게, 컨테이너 시작 시 한 번 만들어진 인스턴스를 계속 재사용하는 방식이다.
즉,
- 요청이 100명이 동시에 와도
- 스레드가 100개 생길 수는 있지만
- 서블릿 객체가 100개 생기는 게 아니라
- 하나의 서블릿 객체를 100개 스레드가 동시에 호출하는 구조가 된다.
그래서 자연스럽게 따라오는 결론이 있다.
서블릿 클래스에서 인스턴스 필드(멤버 변수)에 상태를 저장하면
여러 스레드가 동시에 건드리면서 문제가 생길 수 있다.
서블릿이 멀티스레드 환경에서 돌아가기 때문에 “서블릿은 기본적으로 상태를 가지면 위험하다”라는 말이 여기서 나온다.
정리
서블릿 컨테이너는 단순히 “서블릿을 실행해주는 서버”가 아니다.
- 서블릿을 찾고(URL 매핑)
- 서블릿을 만들고(인스턴스 생성)
- 초기화하고(init)
- 요청마다 호출하고(service)
- 종료 시 정리하고(destroy)
이 전체를 책임지는 생명주기 관리자다.
그리고 멀티스레드 환경에서 서블릿 인스턴스는 보통 하나만 생성되어 공유된다는 점은
서블릿을 제대로 이해하는 데 가장 중요한 핵심 중 하나다.
출처 : 《자바 웹 프로그래밍 Next Step》, 박재성, 로드북
'Book > 자바 웹 프로그래밍 Next Step' 카테고리의 다른 글
| 6장_ : ListUser 화면 구현 (0) | 2026.02.06 |
|---|---|
| 6장_ : ListUSerServlet 구현 (0) | 2026.02.06 |
| 5장_ : Servlet으로 Hello World 출력하기 (0) | 2026.02.05 |
| 5장_: 임베디드 톰캣으로 웹 서버 띄우기 (0) | 2026.02.05 |
| 3장_ 로깅: System.out.println() 대신 로깅을 써야 하는 이유 (0) | 2026.02.02 |
