BACKEND [Kamranahmedse Roadmap]/JSP - Servlet

Servlet 생명주기 (Lifecycle)

Alex Han 2025. 2. 8. 00:04
반응형

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. 상속 구조 때문입니다.

HttpServletservice() 메서드를 이미 구현하고 있으며, 요청 방식에 따라 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()만 구현하면 됨

 

결론

HttpServletservice()를 구현해 두었기 때문에, 우리는 doGet(), doPost()만 구현하면 된다.

직접 service()를 오버라이드할 필요가 없다.

 

 


🚀 최종 정리

1. 서블릿 객체는 한 번만 생성되며, init()에서 DB 연결 및 설정값 로드를 수행해야 한다.

2. service()를 직접 구현할 필요가 없으며, doGet()과 doPost()만 오버라이드하면 된다.

3. 서블릿은 싱글톤이므로 멤버 변수 사용 시 동시성 문제에 주의해야 한다.

4. destroy()에서는 반드시 리소스 해제(DB 연결 닫기 등)를 수행해야 한다.

 

이제 서블릿을 효율적으로 관리하는 방법을 이해했을 것이다! 🚀

반응형