서블릿(Servlet)

Java 코드 안에 HTML 코드를 작성할 수 있는 Java 프로그램이다.
Thread에 의해 서블릿에 있는 service()메소드가 호출된다.
전송방식 요청에 맞게 doGet() 또는 doPost()메소드를 호출한다.

  • servlet은 서버 쪽에서 실행되면서 클라이언트의 요청에 따라 동적으로 서비스를 제공하는 자바 클래스이다.
  • 독자적으로 실행할 수 없으며, 톰캣과 같은 JSP/Servlet 컨테이너에서만 실행 가능하며, 컨테이너 독립적으로 실행된다.
  • 서블릿은 서버에서 실행되다가 웹 브라우저에서 요청을 하면 해당 기능을 수행 후, 웹 브라우저에 결과를 전송한다.

서블릿과 서버

과거 정적인 웹페이지만 자료를 제공할 때는 서버의 구조도 단순했다.

이미 저장된 페이지를 반환하기만 하면 됐기때문이다.

이제는 업그레이드가 되어서 정적인 페이지만 반환하는 것이 아니다.

서버가 업그레이드 되면서 연산 기능까지 가지게 됐다. 그렇게 되면서 과거에는 서버라고 불리던 것은 크게 WEB서버와 WAS로 나눠졌다. WEB은 사용자의 요청에 따라 정적인 페이지를 제공하고 WAS는 사용자의 요청 중 연산이 필요한 부분을 맡아서 그 결과를 계산한다. 그리고 WAS는 연산 결과를 웹 서버로 제공하고 웹 서버 정적 페이지를 만들어 사용자에게 전달한다. 이 때 WAS에서 연산을 담당하는것이 서블릿이다. 서블릿은 WAS안에 있는 서블릿 컨테이너 또는 웹 컨테이너라고 불리는 공간에서 활용하게 된다.

서블릿 구조

서블릿은 클래스이자 하나의 자바 패키지이다. 서블릿은 자바 클래스의 형태를 가지고 있다. 즉, 서블릿은 자바 언어로 쓰여지고 자바를 기반으로 하는 프로그램이다. 이러한 서블릿 클래스는 javax.servlet.http라는 패키지 안에 포함되어 있다. javax.servlet 패키지는 서블릿 구현을 위한 다양한 인터페이스와 클래스가 포함된 꾸러미다. 그리고 서블릿 클래스는 이 패키지 안에서 하나를 빌려와서 구현하는 것이다.

서블릿 패키지 내 구조

서블릿클래스를 불러오면 다음과 같은 코드로 되어있다.

선언부를 보면 알 수 있듯이 우리가 사용하는 서블릿은 HttpServlet을 상속 받아 구현하고 있다.

서블릿 클래스를 사용하기까지 구조를 보자면 이렇게 된다.

  1. Servlet 인터페이스
    각종 서블릿 클래스를 구현하는 가장 기본 토대가 되는 인터페이스이고 해당 인터페이스를 구현하면 서블릿 클래스를 구현하여 사용할 수 있다. 해당 인터페이스를 구현하기 위해서는 아래 5개의 메서드를 구현해야 한다. 이때 메서드는 life-cycle 메서드라고 한다.
  1. ServletConfig 인터페이스
    초기화 중에 서블릿에 정보를 전달하기 위해 서블릿 컨테이너에서 사용하는 서블릿 구성을 위한 객체
  1. GenericServlet 추상 클래스
    Servlet 인터페이스와 ServletConfig 인터페이스를 구현하여 만든 추상 클래스이다. 기존 Servlet 인터페이스를 구현하기 위해서는 일일이 메소드를 따로 만들어야 했다. 게다가 ServletConfig 인터페이스까지 같이 구현해야 했는데 이를 조금 더 수월하게 하기 위해서 만든 것이 GenericServelt 추상 클래스이다. GenericServlet은 Servlet의 라이프사이클 메소드 중 init(), destory()를 간단하게 제공한다. 즉, 사용자는 service()만 구현하여 서블릿을 간편하게 실행할 수 있다.
  1. HttpServlet 추상 클래스
    GenericServlet를 상속하여 만든 추상 클래스다. HttpServlet는 GenericServlet과 마찬가지로 상속받으면 간단하게 서블릿을 실행할 수 있다. 그리고 우리가 대부분 사용하는 서블릿은 HttpServlet 추상 클래스를 상속받은 클래스다. GenericServlet이 init(), destroy()까지 구현했다면 HttpServlet는 service()까지 구현되있다. 설정상으로는 건드릴 필요가 없을 정도로 구체화 정도가 높은 편이다. 다만 이름에서 알 수 있듯이 GenericServlet은 http뿐 아니라 다양한 프로토콜에 대응이 가능하다. 즉 Http 프로토콜을 이용하지 않을 때도 사용이 가능하다. 하지만 HttpServlet는 웹개발서 주로 쓰는 프로토콜이 http라서 이를 편하게하려고 만들었다. 그래서 HttpServlet는 Http 프로토콜에만 한정적으로 사용할 수 있다. 단, 이때 주의할 것이 HttpServlet가 상속받는 GenericServlet는 javax.servlet의 패키지다. 하지만 우리가 흔히 쓰는 서블릿 클래스는 HttpServlet을 상속받는데 이 추상클래스는 Http 프로토콜 대응을 위한 서블릿 전용 패키지인 javax.servlet.http 패키지에 속해있다. 상속도를 봤을 때 GenericServlet 추상클래스까지는 javax.servlet의 패키지에 속한다. 그리고 우리가 흔히 상속받는 HttpServlet 추상클래스는 javax.servlet.http의 패키지에 속한다.

서블릿 동작 과정

서블릿이란 Dynamic Web Page를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술입니다. 웹을 만들 때는 다양한 요청(Request)응답(Response)이 있기 마련이고 이 요청과 응답에는 규칙이 존재합니다. 이러한 요청과 응답을 일일이 처리하려면 굉장히 힘든데, 서블릿은 이러한 웹 요청과 응답의 흐름 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 해주는 기술이다.

서블릿은 자바 클래스로 웹 애플리케이션을 작성한 뒤 이후 웹 서버 안에 있는 웹 컨테이너에서 이것을 실행하고, 웹 컨테이너에서는 서블릿 인스턴스를 생성 후 서버에서 실행되다가 웹 브라우저에서 서버에 요청(Request)을 하면 요청에 맞는 동작을 수행하고 웹 브라우저에 HTTP형식으로 응답(Response)합니다.

  1. 클라이언트의 요청
    클라이언트는 HTTP프로토콜을 통해서 원하는 데이터를 요청(Request)한다. 이때 고객의 요청이 정적 페이지 요청이라면 WEB서버를 통해서 바로 처리해준다. 반대로 동적인 페이지에 대한 요청이라면 고객의 요청은 웹 컨테이너(서블릿 컨테이너)로 넘어간다.

  2. HttpServletRequest, HttpServletReponse 객체 생성
    HttpServletRequest 객체는 고객의 요청 정보를 담은 객체로서 웹 컨테이너에 의해 생성된다. 이 객체는 웹 컨테이너에서 고객 요청 정보를 담고 해당 정보가 처리될때까지 웹 컨테이너를 돌아다닌다. 반대로 HttpServletReponse 객체는 요청에 따른 처리 정보를 담은 객체로서 웹 컨테이너에 의해 생성된다. 그리고 이 객체는 고객 요청의 처리에 따라 떠돌다가 최종적으로는 고객의 요청에 응답하게 된다. 고객의 요청에 따른 데이터 처리를 위한 흐름은 HttpServletRequest객체가 처리한다. 그리고 데이터 처리 후 결과물을 반환하기 위한 흐름은 HttpServletReponse 객체가 처리한다.

  3. Web.xml 파싱
    배포 서술자(deployment descriptor)라고도 불리는 Web.xml은 서버를 초기화 할때 서블릿의 위치가 어디있는지 설정을 도와주는 문서다. 즉 어떤 서블릿이 프로젝트 폴더 내에 있다면 어느 위치에 존재하고 어떤 URL로 접속해야하는지 알려준다. 컨테이너는 고객 요청 정보를 파악한 이후에 Web.xml 문서에 따라 그에 맞는 서블릿 주소를 찾는다. 그리고 서블릿에 접근을 하게 된다.

  4. 서블릿 초기화
    컨테이너가 실행할 서블릿을 찾았다면 이제 해당 서블릿 클래스를 로드하게 된다. 즉, 서버에 불러온다. 그리고 서블릿에서 사용할 객체를 만든다. 그리고 이 객체를 이용해 init() 메서드를 호출하여 서블릿을 사용할 수 있도록 초기화한다. 이때 매우 중요한 점은 위 과정은 한 개의 서블릿이 처음 요청될 때 수행된다는 점이다. 서블릿은 사용자가 요청할 때마다 새로 초기화되지 않는다. 클래스 로드, 서블릿 인스턴스 생성, init()은 서블릿 생명 주기에서 단 한번만 수행된다. 이때 만들어지는 인스턴스는 싱글톤 패턴에 따른 것으로 주기 내내 단 한번 만들어진다. 그리고 많은 요청이 다중으로 들어와도 이 한 개의 인스턴스를 사용해 service() 메서드를 실행한다. 이는 스레드 생성에 따라 가능한 것이다. 서블릿이 최초 요청에 따라 초기화 및 실행된 이후에는 고객의 요청은 새로운 스레드를 만들어서 service() 메서드를 실행하는 식으로 이뤄진다. 이로 인해 불필요한 자원 낭비를 막을 수 있고 효율적인 자원 관리가 가능해진다. 단, 이렇게 서블릿을 이용할 경우 최초 요청을 한 사용자는 요청에 따른 딜레이라 발생할 수 있다. 하지만 이는 설정을 통해 극복할 수 있다. 모든 서블릿의 로드와 초기화를 사용자 요청 없이도 서버 시작과 동시에 진행하도록 설정할 수 있다. 이렇게 설정할 경우 최초 사용자라하더라도 딜레이없이 요청에 대한 처리를 받을 수 있다.

  5. service() 실행
    초기화가 진행된 이후에는 고객의 요청을 실질적으로 처리하는 service()메서드가 실행된다. service()는 초기화가 진행되어야 실행된다. 그리고 service()는 고객 요청이 get방식이냐 post방식이냐에 따라 거기에 맞는 메서드를 실행한다. 고객의 요청이 get방식으로 전달됐다면 doGet() 메서드를 실행한다. 고객의 요청이 post방식으로 전달됐다면 doPost() 메서드를 실행한다. 이전에도 말했듯이 서블릿은 생명 주기에서 초기화가 단 한번 진행된다. 그리고 나선 요청에 따라 스레드를 생성하여 service() 메서드를 실행하게 된다.

  6. destroy() 실행
    더이상 사용되지 않는다고 판단하거나 서버가 종료될 때 컨테이너는 destroy()를 실행한다. destroy() 메서드는 초기화되어 실행 중인 servlet을 제거하는 역할을 한다. 서버가 종료되거나 오랫동안 서블릿이 사용되지 않는다고 판단되면 실행된다. 즉, JVM의 가비지 컬렉터(GC)의 기능을 하는 메서드라고 보면 된다. 일반적으로 컨테이너가 자동적으로 실행하고 관리하게 된다.

Servlet Container

Servlet Container는 Servlet을 이용해 작성된 프로그램을 실행, 관리해주는 주체이다. 서블릿을 이용해 사용자 요청을 처리하는 역할을 한다. 보통 일반적으로 한 가지 기능만 제공하는 홈페이지는 없다. 쇼핑몰을 예를 들자면, 상품등록, 장바구니, 게시판, 회원 가입 등 다양한 기능을 한다. 그렇다는 이야기는 각각의 기능을 구현할 다양한 서블릿이 한 서버 안에서 작동한다는 것이다. 웹컨테이너(서블릿 컨테이너)는 이러한 다양한 서블릿이 고객의 요청에 따라 작동하도록 제어한다. 이를 통해 효율적으로 서버 관리 및 사용자 요청을 처리할 수 있다. 서블릿 컨테이너는 구현되어 있는 servlet 클래스의 규칙에 맞게 서블릿은 관리해주며 클라이언트에서 요청을 하면 컨테이너는 HttpServletRequest, HttpServletResponse 두 객체를 생성하며 post, get여부에 따라 동적인 페이지를 생성하여 응답을 보냅니다.

서블릿 컨테이너는 서블릿을 각각의 요청별로 생성해둔다. 예를 들어 웹 서비스가 로그인, 로그아웃, 글 읽기, 글 쓰기 이 4개의 요청을 처리해야한다면, 서블릿 컨테이너는 4개의 개별적인 서블릿 객체를 생성해둔다. 각 요청별 서블릿은 단 하나만 생성한다. 그렇다고, 서블릿 자체가 싱글톤이라는 이야기는 아니다.

클라이언트로부터 서블릿 컨테이너가 요청을 받으면, Request 객체, Response 객체를 생성한다. 그리고 URL에 따라 요청을 처리할 적절한 서블릿을 찾는다. 서블릿 컨테이너는 찾은 서블릿의 service() 메소드를 실행하는데 이때 생성해둔 Request, Response 객체를 파라미터로 함께 전달한다. 서블릿이 요청을 처리하면, Response 객체를 다시 서블릿 컨테이너에게 전달하고, 사용자에게 응답한다.

이때, service() 메소드는 별개의 쓰레드로 실행된다. 즉, 서블릿 객체는 하나이지만 여러개의 쓰레드에서 실행된다. 이것이 웹 서버가 여러 사용자의 요청을 동시에 처리할 수 있는 이유이다. 서블릿 객체는 하나인데, 쓰레드가 여러개라면 동시성 이슈가 발생할 가능성이 있다. 이런 구조상 Thread-Safe를 위해 서블릿은 상태(인스턴스 및 Static 변수 등)를 가지면 안된다. 서블릿이 상태를 가지면, 공유 자원으로 인한 동시성 이슈가 발생해 사용자의 민감한 정보가 다른 사용자에게 노출되는 일이 발생할수도 있다. 요청을 모두 처리했다면 사용된 쓰레드와 Request, Response 객체는 JVM에게 반납한다. 이때 Servlet은 계속 살아있다.

1. 웹서버와의 통신 지원
서블릿 컨테이너는 서블릿과 웹서버가 손쉽게 통신할 수 있게 해준다. 서블릿 컨테이너는 소켓을 만들고 listen, accept등의 기능을 API로 제공하여 복잡한 과정을 생략할 수 있게 해준다. 개발자가 서블릿에 구현해야할 비지니스 로직에 대해서만 초점을 두게끔 도와준다.

2. 서블릿 생명주기 관리
서블릿 컨테이너는 서블릿의 실행부터 종료까지의 과정을 관리한다. 서블릿 클래스를 로딩하여 인스턴스화하고, 초기화 메소드를 호출하고, 요청이 들어오면 적절한 서블릿 메소드를 호출한다. 또한 서블릿이 생명을 다 한 순간에는 적절하게 Garbage Collection(가비지 컬렉션)을 진행하여 안전하게 프로그램을 종료한다.

3. 멀티스레딩 관리
서블릿 컨테이너는 해당 서블릿의 요청이 들어오면 스레드를 생성해서 작업을 수행합니다. 그렇기에 동시에 여러 요청이 들어와도 멀티스레딩 환경으로 동시다발적인 작업을 관리할 수 있습니다. 또한 이렇게 한번 메모리에 올라간 스레드는 다시 생성할 필요가 없기에 메모리 관리에 효율적입니다.

4. 선언적인 보안관리
서블릿 컨테이너는 보안 관련된 기능을 지원합니다. 그렇기에 서블릿 또는 자바 클래스 안에 보안 관련된 메서드를 구현하지 않아도 됩니다. 대체적으로 보안관리는 XML 배포 서술자에 기록하기 때문에 보안이슈로 소스를 수정할 일이 생겨도 자바 소스 코드를 수정하여 다시 컴파일 하지 않아도 됩니다.

웹 컨테이너에서 서비스 실행

앞서 말했듯 service() 메서드를 실행하기 위해서는 최초 요청에만 초기화를 단 한번 진행한다고 했다. 그림으로 나타내면 위와 같다. 컨테이너에서 서블릿에 대한 최초 요청이 있을 때만 서블릿을 초기화하는 작업을 한다. 그 뒤에는 다른 사용자가 해당 서블릿에 대해 요청할 경우에 스레드를 만들어서 대응한다. 이렇게 스레드를 이용해 최초에 만든 인스턴스 하나로 서블릿의 service()메서드를 실행하여 처리한다. 단, 서버 구동시 미리 서블릿에 대한 초기화를 미리 진행하면 최초 사용자도 초기화없이 사용이 가능하다.

profile
발전하기 위한 공부

0개의 댓글