1️⃣ 서블릿 개념 및 특징
1) 개념
서블릿(Servlet) 이란 자바를 사용하여 웹 페이지를 동적으로 생성할 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술이다.
서블릿은 자바 코드 안에 HTML을 포함하며, 웹 요청 및 응답의 흐름을 체계적으로 다룰 수 있게 해준다.
만약 클라이언트가 서버에 요청을 하면, 서블릿에서 해당 기능을 수행한 뒤 결과를 다시 전송한다.
서블릿의 흐름을 간략히 나타내면 다음과 같다.
- 웹 브라우저 (클라이언트) 에서 서버로 요청을 보낸다.
- WAS 에서 HTTP 요청 메시지를 기반으로 request, response 객체를 생성한다.
- 미리 생성해놓은 Thread Pool 에서 하나의 Thread 를 할당 받는다.
- 할당 받은 Thread 는 서블릿 인스턴스에 접근, 실행한다.
- 요청에 맞는 동작을 수행하고 다시 HTTP 형식으로 응답한다.
이러한 구조를 통해 서블릿의 몇 가지 특징을 알 수 있다.
2) 주요 특징
- 클라이언트의 HTTP Request 에 대해 동적으로 작동하는 웹 애플리케이션 컴포넌트이다.
- Java Thread 를 이용하여 동작한다. Multi-Thread 환경에서도 관리가 가능하다.
- MVC 패턴에서 Controller 로 이용할 수 있다.
- Servlet Container 내에서 실행 된다.
- 보안 관련 기능을 쉽게 적용할 수 있다.
2️⃣ 서블릿 동작 과정
1) 서블릿 컨테이너 (Servlet Container)
서블릿을 갖고 있는 컨테이너, 즉 서블릿의 모음이다.
이미 구현되어 있는 서블릿 클래스의 규칙에 맞게 서블릿을 관리하며, 클라이언트의 요청으로부터 HttpServletRequest, HttpServletResponse 두 객체를 생성한다. 이후 HTTP method 에 따라 동적인 페이지를 생성하여 응답을 보낸다.
특히 Tomcat 등 서블릿을 지원하는 WAS 를 '서블릿 컨테이너' 라고 한다.
- 특징
- 서블릿 객체는 싱글톤으로 관리 된다. -> 최초 로딩 시점에 서블릿 객체를 미리 만들어 두고 재활용한다.
- 모든 요청은 동일한 서블릿 객체 인스턴스에 접근 한다. -> 공유 변수 사용에 주의해야 한다.
- 서블릿 컨테이너 종료시 서블릿 객체 또한 함께 종료된다.
- HttpServletRequest
- 인터페이스이며 'ServletRequest'를 상속 받는다.
- 클라이언트로부터 HTTP Request 를 받아 정보를 서블릿에게 전달하기 위해 사용한다.
- 헤더 정보, 파라미터, 쿠키, URL, URI 등의 정보가 있다.
- getAuthType(), getCookies(), getDateHeader(), getHeader(), getHeaderNames(), getMethod() 등을 담고 있다.
- HttpServletResponse
- 인터페이스이며 'ServletResponse'를 상속 받는다.
- 요청에 맞는 동작을 수행하고, 해당 정보를 클라이언트에게 응답하기 위해 사용한다.
- content-type, 응답 코드, 응답 메시지 등의 정보가 있다.
- addCookie(), containsHeader(), encodeURL(), setHeader(), setStatus(), getHeader() 등을 담고 있다.
2) 서블릿 컨테이너 주요 기능
1. 웹 서버와의 통신 지원
실제로 클라이언트가 웹 서버와 통신을 하기 위해선 TCP/IP 및 소켓 연결, HTTP 요청 메시지 파싱 후 읽기, HTTP method 확인, HTTP 메시지 바디 내용 파싱 후 읽기 등의 과정을 거쳐야 한다. 하지만 서블릿 컨테이너는 이러한 기능을 API 로 제공하여 복잡한 과정을 줄이고, 개발자가 비즈니스 로직에 집중할 수 있도록 도와준다.
2. 서블릿 생명 주기 관리
서블릿의 생명 주기를 관리한다. 서블릿 컨테이너가 실행되면 서블릿 클래스를 로딩하여 인스턴스화하고, 초기화 메소드를 호출한다.
이후 요청이 들어오면 목적에 맞는 서블릿 메소드를 호출한다. 또한 수명이 끝난 서블릿을 가비지 콜렉터를 통해 제거하여 자원을 효율적으로 관리할 수 있도록 도와준다.
3. 동시 요청을 위한 멀티 쓰레드 처리 지원 및 관리
필요한 쓰레드를 쓰레드 풀에 보관하고 관리한다. 서블릿 컨테이너는 요청이 올 때마다 쓰레드 풀에서 쓰레드를 꺼내서 사용한다. 이후 사용을 종료하면 쓰레드 풀에 해당 쓰레드를 반납한다. 따라서 쓰레드를 생성 및 종료하는 비용이 절약되고, 시간이 빠르다. 또한 요청이 너무 많을 경우 쓰레드의 임계점이 존재하므로 기존 요청은 안전하게 처리, 관리할 수 있다. 멀티 쓰레드에 대한 부분은 WAS 가 처리해주기 때문에 개발자는 이와 관련된 코드를 신경쓰지 않아도 된다.
4. 보안 관리
보안과 관련된 기능을 지원한다. 따라서 개발자는 보안과 관련된 기능을 따로 구현하지 않아도 된다. 대체로 보안 관리는 XML 배포 서술자에 기록하므로, 해당 내용과 관련된 소스 코드를 수정할 일이 있어도 직접 코드를 수정하거나 다시 컴파일하지 않아도 된다.
3️⃣ 서블릿 생명 주기
1) 생명 주기 (Life Cycle)
1. 클라이언트가 WAS 에 서블릿 요청을 보내면 WAS 는 해당 서블릿이 메모리에 존재하는지 확인
2-1. 메모리에 존재하지 않는 경우 : 서블릿 클래스를 메모리에 올리고 init(), service() 메서드 실행
2-2. 메모리에 존재하는 경우 : service() 메서드 실행
3. WAS 가 종료되거나 웹 애플리케이션 갱신 등으로 서블릿 종료 요청시 destroy() 메서드 실행
2) 생명 주기 메서드
- init() : 서블릿을 처음 메모리에 올릴 때 실행되어 서블릿을 초기화하여 처음에 한 번만 실행
- service() : request, response 를 처리하여 HTTP method 확인
- GET : doGet() 실행
- POST : doPost() 실행
- destroy() : 서블릿 종료 요청이 있을 때 실행
3) 생명 주기 확인을 위한 코드
@Slf4j
@WebServlet(name = "requestBasicServlet", urlPatterns = "/request-basic")
public class RequestBasicServlet extends HttpServlet {
public RequestBasicServlet() {
super();
log.info("[Constructor] - Generate Servlet");
}
@Override
public void init(ServletConfig config) throws ServletException {
log.info("[init] - Initialize Servlet");
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.info("[service] - Check HTTP Method");
String method = request.getMethod();
log.info("method = {}", method);
super.service(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.info("[doGet] - GET Request");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.info("[doPost] - POST Request");
}
@Override
public void destroy() {
log.info("[destroy] - End Servlet");
}
}
1. Spring Boot 실행시
2. 해당 URL로 접속시 (http://localhost:8080/request-basic) - GET
3. 다시 해당 URL로 접속시 - GET
4. Postman 으로 POST 요청시 - POST
위에서 공부한 내용을 토대로 서블릿의 생명 주기를 확인했다.
- Spring Boot 시작시 서블릿 생성자가 호출되는 것을 알 수 있다.
- URL 접속으로 GET 요청을 보냈다. 해당 서블릿이 이전까지 메모리에 존재하지 않았기에 'init()' 메서드가 실행되었다. 이후 'service()' 메서드가 실행되었다.
- 다시 같은 URL 접속시 'init()' 메서드는 실행되지 않았다. 대신 'service()' 메서드는 실행되었다.
- 'service()' 메서드 실행 이후 HTTP method 를 확인, 출력한다. 이후 요청에 따라 'doGet()', 'doPost()' 메서드가 실행되었다.
- 즉, constructor -> init() (처음 실행시) -> service() -> doGet() or doPost .. -> service() -> .. 의 순서대로 관리된다는 것을 알 수 있었다.