Servlet 이란?

2coconut·2025년 7월 3일

정적 리소스 vs 동적 리소스

웹 개발을 이해하기 위해서는 먼저 정적 리소스동적 리소스의 차이를 알아야 한다.

정적 리소스 (Static Resources)

정적 리소스는 서버에 미리 저장되어 있는 파일들로, 요청이 들어오면 그대로 전송되는 콘텐츠다. HTML 파일, CSS 파일, JavaScript 파일, 이미지(JPG, PNG, GIF), 동영상, PDF 등이 대표적이다.

특징:

  • 서버에서 별도의 처리 없이 파일 그대로 전송
  • 모든 사용자에게 동일한 내용 제공
  • 빠른 응답 속도
  • 캐싱이 용이함

동적 리소스 (Dynamic Resources)

동적 리소스는 사용자의 요청이나 상황에 따라 실시간으로 생성되는 콘텐츠다. 사용자가 로그인했을 때 보이는 개인화된 페이지, 검색 결과, 게시판 목록, 쇼핑몰의 상품 정보 등이 해당된다.

특징:

  • 요청 시점에 서버에서 프로그램을 실행하여 생성
  • 사용자나 상황에 따라 다른 내용 제공
  • 데이터베이스 조회, 계산, 외부 API 호출 등이 필요할 수 있음
  • Servlet, JSP, PHP, ASP 등의 서버 사이드 기술로 구현

예시:

  • 정적: company.html (회사 소개 페이지)
  • 동적: board/list?page=1 (게시판 1페이지 - 실시간 데이터베이스 조회 결과)

Servlet은 바로 이 동적 리소스를 생성하는 핵심 기술 중 하나다.

Servlet이란 무엇인가

Servlet은 Java 언어로 작성된 서버 사이드 프로그램이다. 쉽게 말해, 웹 브라우저에서 요청이 들어오면 그 요청을 받아서 처리하고, 적절한 응답을 돌려주는 Java 클래스라고 볼 수 있다.

더 정확히 말하면, Servlet은 javax.servlet.Servlet 인터페이스를 구현한 클래스다. 일반적으로는 HTTP 프로토콜을 처리하기 위해 HttpServlet 클래스를 상속받아서 구현한다. Servlet은 단순한 Java 클래스가 아니라, 웹 서버 환경에서 동작하는 특별한 컴포넌트로서, 클라이언트의 요청을 받아서 동적인 웹 페이지를 생성하거나 데이터를 처리하는 역할을 담당한다.

Servlet이 탄생한 배경

초기 웹 개발에서는 CGI(Common Gateway Interface)를 사용했다. 하지만 CGI는 요청마다 새로운 프로세스를 생성해야 해서 성능이 좋지 않았다. 특히 동시 접속자가 많은 환경에서는 시스템 리소스가 급격히 소모되어 서버가 다운되는 경우도 빈번했다.

이런 문제를 해결하기 위해 Java 진영에서 만든 것이 바로 Servlet이다. Servlet은 CGI와 달리 프로세스가 아닌 스레드 방식으로 동작한다. 한 번 메모리에 로딩되면 계속 상주하면서 여러 요청을 처리할 수 있어 훨씬 효율적이다. 또한 JVM의 강력한 메모리 관리와 가비지 컬렉션 기능을 활용할 수 있어 메모리 누수 문제도 크게 줄어들었다.

Servlet Container의 역할

Servlet은 혼자서는 동작할 수 없다. Servlet Container (또는 Web Container)라는 실행 환경이 필요하다. Servlet Container는 Servlet의 실행 환경을 제공하는 소프트웨어로, Java EE 사양의 핵심 구성 요소 중 하나다.

Servlet Container의 주요 기능

생명주기 관리
Servlet Container는 Servlet 클래스를 메모리에 로딩하고, 인스턴스를 생성하며, 초기화부터 소멸까지 전체 생명주기를 관리한다. 개발자는 Servlet 클래스만 작성하면 되고, 언제 생성하고 언제 호출할지는 Container가 알아서 처리한다. 이는 Inversion of Control(제어의 역전) 원칙을 따르는 것으로, 개발자가 객체의 생성과 소멸을 직접 관리하지 않아도 된다는 큰 장점이 있다.

네트워크 서비스 제공
HTTP 요청을 수신하고 응답을 전송하는 모든 네트워크 관련 작업을 담당한다. 소켓 관리, 스레드 풀 관리 등 복잡한 네트워크 프로그래밍을 Container가 대신 처리해준다. TCP/IP 소켓 연결, HTTP 프로토콜 파싱, 요청 헤더 분석 등의 저수준 작업들을 개발자가 신경 쓰지 않아도 된다.

멀티스레딩 지원
동시에 여러 클라이언트의 요청이 들어와도 각각을 독립적인 스레드로 처리할 수 있게 해준다. 하지만 여기서 중요한 점은 Servlet 인스턴스는 보통 하나만 생성되고, 여러 스레드가 이 하나의 인스턴스를 공유한다는 것이다. 따라서 Servlet 개발 시에는 스레드 안전성(Thread Safety)을 반드시 고려해야 한다. 인스턴스 변수 사용을 피하고, 지역 변수나 메서드 파라미터를 활용하는 것이 중요하다.

보안 관리
인증 및 권한 처리, HTTPS 지원 등 보안과 관련된 다양한 기능을 제공한다. 선언적 보안과 프로그래밍적 보안 모두를 지원하며, 사용자 인증, 역할 기반 권한 부여, SSL/TLS 암호화 등을 처리한다.

설정 관리
web.xml(배포 서술자) 파일이나 애노테이션을 통한 설정 정보를 해석하고 관리한다. URL 매핑, 초기화 파라미터, 필터 설정, 에러 페이지 설정 등을 포함한다.

대표적인 Servlet Container로는 Apache Tomcat, Jetty, JBoss, WebLogic, WebSphere 등이 있다. 이 중에서도 Tomcat은 가장 널리 사용되는 오픈소스 Servlet Container다.

Servlet의 생명주기

Servlet의 생명주기는 크게 3단계로 나뉜다. 이 생명주기를 정확히 이해하는 것은 Servlet 개발에서 매우 중요하다.

초기화 단계 (init)

Servlet이 처음 메모리에 로드될 때 한 번만 실행된다. 정확히는 Container가 Servlet 클래스를 로딩하고 인스턴스를 생성한 직후에 init() 메서드가 호출된다. 이 단계에서는 데이터베이스 연결 풀 설정, 설정 파일 읽기, 리소스 초기화, 외부 서비스 연결 등의 작업을 수행한다.

init() 메서드는 ServletConfig 객체를 파라미터로 받는데, 이 객체를 통해 web.xml에서 정의한 초기화 파라미터에 접근할 수 있다. 또한 ServletContext 객체에도 접근할 수 있어서 애플리케이션 전역 정보를 얻을 수 있다. 한 번 초기화되면 해당 Servlet 인스턴스는 메모리에 계속 상주하게 된다.

초기화 과정에서 예외가 발생하면 해당 Servlet은 사용할 수 없는 상태가 되고, Container는 이후 요청을 처리하지 않는다.

서비스 단계 (service)

클라이언트의 요청이 들어올 때마다 실행되는 단계다. 이것이 Servlet의 핵심 단계로, 실제 비즈니스 로직이 수행되는 곳이다. service() 메서드는 HttpServletRequest와 HttpServletResponse 객체를 파라미터로 받는다.

HttpServletRequest 객체에는 클라이언트가 보낸 모든 정보가 담겨있다. 요청 파라미터, 헤더 정보, 쿠키, 세션, 요청 방식(GET, POST 등) 등을 포함한다. HttpServletResponse 객체는 클라이언트에게 보낼 응답을 구성하는 데 사용된다. 응답 헤더 설정, 쿠키 추가, 콘텐츠 타입 지정, 실제 응답 데이터 작성 등이 가능하다.

HTTP 메서드(GET, POST, PUT, DELETE 등)에 따라 적절한 메서드로 분기되어 처리된다. 일반적으로 service() 메서드를 직접 오버라이드하기보다는 doGet(), doPost() 등의 메서드를 오버라이드한다.

중요한 점은 service() 메서드는 멀티스레드 환경에서 동시에 여러 번 호출될 수 있다는 것이다. 따라서 스레드 안전성을 고려한 프로그래밍이 필수다.

소멸 단계 (destroy)

Servlet이 메모리에서 제거되기 전에 한 번만 실행된다. 일반적으로 웹 애플리케이션이 종료되거나, Container가 종료될 때, 또는 Container가 메모리 부족으로 Servlet을 언로드할 때 호출된다.

이 단계에서는 연결된 리소스를 정리하고, 열린 파일을 닫고, 데이터베이스 연결을 해제하고, 백그라운드 스레드를 정리하는 등의 정리 작업을 수행한다. destroy() 메서드가 호출된 후에는 해당 Servlet 인스턴스에 대한 모든 참조가 제거되고 가비지 컬렉션의 대상이 된다.

destroy() 메서드는 파라미터를 받지 않으며, 예외가 발생해도 Container는 계속해서 종료 과정을 진행한다.

요청-응답 처리 메커니즘

웹 브라우저에서 URL을 입력하고 엔터를 쳤을 때 실제로 어떤 일이 벌어지는지 알아보자. 이를 식당에서 음식을 주문하는 과정으로 비유해서 설명해보겠다.

단계별 처리 과정

1. 요청 접수 (손님이 식당 입구로 들어옴)
사용자가 브라우저에서 URL을 입력하면, 그 요청이 웹 서버를 거쳐 Servlet Container로 전달된다. 마치 손님이 식당 문을 열고 들어오는 것과 같다.

2. URL 매핑 (어느 테이블로 안내할지 결정)
Container는 들어온 URL을 보고 어떤 Servlet이 처리해야 할지 결정한다. /login이라는 URL이 들어오면 로그인을 담당하는 Servlet으로, /board/list가 들어오면 게시판을 담당하는 Servlet으로 연결한다. 식당 직원이 손님을 적절한 테이블로 안내하는 것과 비슷하다.

3. Servlet 준비 (요리사 준비)
해당 Servlet이 이미 메모리에 있는지 확인한다. 처음 호출되는 Servlet이라면 새로 생성하고 초기화한다. 이미 있다면 그대로 사용한다. 요리사가 주문을 받을 준비가 되어있는지 확인하는 과정이다.

4. 요청서와 응답서 준비
Container는 두 개의 중요한 객체를 만든다:

  • HttpServletRequest: 손님이 무엇을 원하는지 담은 주문서 (요청 정보)
  • HttpServletResponse: 손님에게 무엇을 줄지 적을 영수증 (응답 정보)

5. 실제 처리 (요리 과정)
Container는 Servlet의 service() 메서드를 호출한다. 이때 위에서 만든 요청서와 응답서를 함께 넘겨준다. Servlet은 요청서를 보고 어떤 일을 해야 할지 파악하고, 실제 작업을 수행한 후 결과를 응답서에 적는다.

예를 들어:

  • GET 요청이면 doGet() 메서드가 실행됨 (조회)
  • POST 요청이면 doPost() 메서드가 실행됨 (생성/수정)

6. 응답 반환 (음식 서빙)
Servlet에서 처리가 끝나면, 응답서에 적힌 내용이 다시 브라우저로 전달된다. HTML 페이지, JSON 데이터, 파일 등 무엇이든 될 수 있다. 요리사가 완성된 음식을 손님에게 가져다주는 과정이다.

간단한 예시로 이해하기

사용자가 http://example.com/hello를 입력했다고 가정해보자:

  1. 브라우저가 서버에 "hello 페이지 좀 보여줘" 라고 요청
  2. Container가 "아, hello는 HelloServlet이 담당하네" 하고 확인
  3. HelloServlet이 준비되어 있는지 체크 (없으면 새로 만들고 초기화)
  4. 요청 정보와 응답 용지를 준비
  5. HelloServlet의 doGet() 메서드 실행: "안녕하세요!" HTML 페이지 생성
  6. 완성된 HTML을 브라우저로 전송
  7. 브라우저가 HTML을 받아서 화면에 표시

핵심 포인트

한 번 만들어지면 계속 사용
Servlet은 한 번 생성되면 메모리에 계속 있다가 여러 요청을 처리한다. 매번 새로 만들지 않아서 빠르다.

동시 처리 가능
여러 사용자가 동시에 요청해도 각각 다른 스레드에서 처리된다. 한 사람이 주문하고 있을 때 다른 사람도 동시에 주문할 수 있는 것과 같다.

요청과 응답은 매번 새로 생성
HandlerServletRequest와 HttpServletResponse 객체는 요청이 올 때마다 새로 만들어진다. 주문서와 영수증은 손님마다 새로 작성하는 것과 같다.

이렇게 보면 Servlet의 요청-응답 처리 과정이 우리가 일상에서 경험하는 서비스 과정과 크게 다르지 않다는 것을 알 수 있다.

Servlet의 핵심 특징

플랫폼 독립성

Java로 작성되어 JVM이 있는 곳이라면 어디서든 실행 가능하다. 운영체제나 하드웨어에 관계없이 동일하게 동작한다. 이는 "Write Once, Run Anywhere" 철학을 그대로 따르는 것으로, 개발된 Servlet은 Windows, Linux, Unix 등 어떤 환경에서도 동일하게 실행된다.

성능 최적화

프로세스가 아닌 스레드 기반으로 동작하여 메모리 사용량과 생성 비용을 크게 줄였다. 한 번 로드된 Servlet은 메모리에 상주하여 재사용되므로 매우 효율적이다. CGI에서는 요청마다 새로운 프로세스를 생성했지만, Servlet은 하나의 인스턴스가 여러 요청을 처리한다.

하지만 이로 인해 스레드 안전성(Thread Safety) 문제가 발생할 수 있다. 여러 스레드가 동시에 하나의 Servlet 인스턴스에 접근하기 때문에, 인스턴스 변수를 사용할 때는 주의해야 한다. 인스턴스 변수는 모든 스레드가 공유하므로 동시성 문제가 발생할 수 있다. 따라서 지역 변수나 메서드 파라미터를 사용하는 것이 안전하다.

확장성

HTTP뿐만 아니라 HTTPS, FTP 등 다양한 프로토콜을 지원하며, 개발자가 필요에 따라 커스텀 Servlet을 작성할 수 있다. Servlet API는 확장 가능한 구조로 설계되어 있어, GenericServlet이나 HttpServlet을 상속받아 특정 목적에 맞는 Servlet을 만들 수 있다.

또한 Filter, Listener, Interceptor 등의 확장 메커니즘을 제공하여 공통 기능을 모듈화하고 재사용할 수 있다.

견고성

JVM의 메모리 관리와 예외 처리 시스템을 활용하여 안정적으로 동작한다. 자동 가비지 컬렉션으로 메모리 누수 문제도 해결된다. Java의 강타입 시스템과 컴파일 시점 오류 검출 기능으로 런타임 오류를 줄일 수 있다.

예외 처리도 체계적으로 관리할 수 있다. ServletException, IOException 등의 표준 예외와 함께 커스텀 예외 처리도 가능하다. web.xml에서 에러 페이지를 설정하여 사용자 친화적인 오류 화면을 제공할 수도 있다.

상태 관리의 복잡성

HTTP는 무상태(Stateless) 프로토콜이므로, 사용자의 상태를 유지하기 위해서는 추가적인 메커니즘이 필요하다. Servlet에서는 세션, 쿠키, 히든 필드, URL 재작성 등의 방법을 제공한다.

세션 관리는 특히 중요한데, 서버 메모리에 세션 정보를 저장하므로 메모리 사용량을 고려해야 한다. 세션 타임아웃 설정, 세션 클러스터링, 세션 복제 등의 고려사항이 있다.

Servlet의 한계점

개발 복잡성
순수 Servlet만으로 웹 애플리케이션을 개발하면 매우 복잡해진다. HTML 생성을 위한 PrintWriter 사용, 요청 파라미터 파싱, 세션 관리, 에러 처리 등 모든 것을 직접 구현해야 한다.

유지보수의 어려움
비즈니스 로직과 프레젠테이션 로직이 하나의 클래스에 섞여있어 코드 가독성이 떨어진다. MVC 패턴의 구현도 개발자가 직접 해야 한다.

반복적인 코드
요청 파라미터 검증, 타입 변환, 에러 처리 등의 공통 작업을 매번 반복해서 구현해야 한다.

테스트의 어려움
Servlet Container 환경에서만 실행되므로 단위 테스트가 어렵다. Mock 객체나 테스트용 Container를 사용해야 한다.

profile
컴공학생

0개의 댓글