Servlet은 객체 생성 → 초기화 → 요청 처리 → 소멸의 과정을 거칩니다.
이를 관리하는 주요 메서드는 각각 차례로 init(), service(), doGet()/doPost(), destroy()이며,
이 과정에서 리소스 로드 및 해제와 상속 관계가 중요한 역할을 합니다.
1. Servlet 생명주기 + 리소스 로드
① 객체 생성 (생성자 호출)
• 서블릿 컨테이너(예: Tomcat, Jetty)는 클라이언트 요청이 들어오면 해당 서블릿의 객체를 생성합니다.
• 한 번만 생성되며, 이후 동일한 인스턴스를 재사용합니다.
• 이 시점에서는 아직 리소스를 로드하지 않으며, 복잡한 작업을 수행하지 않는 것이 좋습니다.
public class LifeCycleServlet extends HttpServlet {
public LifeCycleServlet() {
System.out.println("생성자 호출!");
}
}
📌 주의
• 서블릿 객체는 싱글톤(singleton)으로 관리되므로 여러 클라이언트가 공유합니다.
• 따라서 멤버 변수를 상태값으로 사용하면 동시성 문제가 발생할 수 있습니다.
② 초기화 (init()) - 리소스 로드
• 서블릿이 생성된 후 초기화 작업을 수행하는 메서드입니다.
• 한 번만 실행되며, DB 연결, 설정 파일 로드, API 키 로드 등을 수행할 때 사용됩니다.
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("init() 호출! 리소스 로드 중...");
// 예: 데이터베이스 연결 설정
String dbUrl = config.getServletContext().getInitParameter("DB_URL");
System.out.println("DB 연결 설정 완료: " + dbUrl);
}
📌 리소스 로드 예시
• web.xml에서 DB URL을 설정하고, init()에서 가져와 사용할 수 있습니다.
<context-param>
<param-name>DB_URL</param-name>
<param-value>jdbc:mysql://localhost:3306/mydb</param-value>
</context-param>
String dbUrl = getServletContext().getInitParameter("DB_URL");
📌 주의
• init()에서 예외가 발생하면 서블릿이 로드되지 않습니다.
• 서버가 실행될 때 미리 서블릿을 로드하려면 <load-on-startup> 설정을 사용할 수 있습니다.
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.example.MyServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
③ 요청 처리 (service() vs doGet() / doPost())
• 클라이언트 요청이 들어오면 service() 메서드가 실행되며, 요청 방식(GET, POST 등)에 따라 doGet(), doPost() 등을 호출합니다.
• 하지만 대부분의 경우 service()를 직접 오버라이드하지 않고 doGet()과 doPost()만 사용합니다.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet() 호출!");
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.println("<h1>GET 요청 처리</h1>");
out.close();
}
📌 그렇다면 service()를 직접 사용하지 않고 doGet()과 doPost()만 사용해도 되는 이유는?
1. 상속 구조 때문입니다.
• HttpServlet은 service() 메서드를 이미 구현하고 있으며, 요청 방식에 따라 doGet() 또는 doPost()를 호출합니다.
• 우리가 doGet() 또는 doPost()만 오버라이드하면, service()는 이를 자동으로 호출해 줍니다.
2. 직접 service()를 오버라이드하면 HTTP 요청 방식을 수동으로 구분해야 합니다.
• super.service(req, resp);를 호출하지 않으면 doGet()과 doPost()가 실행되지 않습니다.
• 따라서, 직접 service()를 수정하는 것은 비효율적입니다.
✅ service()와 doGet() / doPost() 비교
메서드 | 실행 시점 | 역할 |
service() | 모든 요청 시 | 요청을 분기하여 doGet(), doPost() 등을 호출 |
doGet() | GET 요청 시 | GET 요청을 처리 (예: URL 파라미터 조회) |
doPost() | POST 요청 시 | POST 요청을 처리 (예: 폼 데이터 처리) |
📌 결론
• 99%의 경우 doGet()과 doPost()만 사용하면 된다.
• service()를 오버라이드할 경우 super.service(req, resp);를 반드시 호출해야 한다.
④ 서블릿 종료 (destroy()) - 리소스 해제
• 서블릿이 제거될 때(undeploy 또는 서버 종료 시) 실행됩니다.
• 데이터베이스 연결 해제, 파일 닫기, 캐시 정리 등의 작업을 수행해야 합니다.
@Override
public void destroy() {
System.out.println("destroy() 호출! 리소스 정리 중...");
// 예: DB 연결 해제
}
📌 주의
• destroy()는 서버가 종료될 때 한 번만 실행됩니다.
• 서블릿이 언로드되지 않는 한 실행되지 않으므로, 자원 정리가 필요한 경우 명시적으로 처리하는 것이 좋습니다.
2. Servlet의 상속 구조
HttpServlet을 상속받아 doGet(), doPost()만 구현하면 되는 이유는 상속 구조 때문입니다.
Servlet (인터페이스)
↑
GenericServlet (추상 클래스)
↑
HttpServlet (추상 클래스)
↑
사용자 정의 서블릿 (ex: MyServlet)
📌 각 클래스의 역할
1. Servlet (인터페이스)
• 모든 서블릿이 구현해야 할 기본 메서드 (init(), service(), destroy())를 정의
2. GenericServlet (추상 클래스)
• service() 메서드를 직접 구현
• HttpServlet의 부모 클래스
3. HttpServlet (추상 클래스)
• HTTP 요청을 처리하도록 service()를 구현
• HTTP 요청 방식에 따라 doGet(), doPost() 등으로 분기
4. 사용자 서블릿 (extends HttpServlet)
• doGet(), doPost()만 구현하면 됨
✅ 결론
• HttpServlet이 service()를 구현해 두었기 때문에, 우리는 doGet(), doPost()만 구현하면 된다.
• 직접 service()를 오버라이드할 필요가 없다.
🚀 최종 정리
1. 서블릿 객체는 한 번만 생성되며, init()에서 DB 연결 및 설정값 로드를 수행해야 한다.
2. service()를 직접 구현할 필요가 없으며, doGet()과 doPost()만 오버라이드하면 된다.
3. 서블릿은 싱글톤이므로 멤버 변수 사용 시 동시성 문제에 주의해야 한다.
4. destroy()에서는 반드시 리소스 해제(DB 연결 닫기 등)를 수행해야 한다.
이제 서블릿을 효율적으로 관리하는 방법을 이해했을 것이다! 🚀
'BACKEND [Kamranahmedse Roadmap] > JSP - Servlet' 카테고리의 다른 글
JSP 생명주기 (Lifecycle) (1) | 2025.02.28 |
---|---|
JSP란 무엇인가? (0) | 2025.02.25 |
HttpServletRequest와 HttpServletResponse의 개념과 동작 방식 (0) | 2025.02.09 |
Servlet 작성방법 (1) | 2025.02.07 |
Servlet이란 무엇인가? (0) | 2025.02.06 |