최근에 아는 지인이 한 기업의 면접을 보게 되었다.
그리고 해당 기업의 면접 질문에서 Servlet과 Servlet Container 가 무엇인지 묻는 질문과 함께 Servlet이 왜 필요한지 그리고 서블릿 컨테이너가 하는 역할은 무엇인지와 같이 서블릿&서블릿 컨테이너와 관련된 개념에 대한 질문을 받았다고 공유해주었다.
마침 우아한테크코스에서 관련된 강의를 듣게 되었고, 이를 정리해보려고 한다.
그런데..자바로 웹 서비스를 개발할 때 보통 스프링만 쓰게 된다..
스프링 MVC
는 서블릿 기반으로 동작한다. 스프링 MVC 내부 구조를 보면 DispatcherServlet
이 있는데, 이것이 바로 서블릿이다. 또 서블릿 스펙 중에는 Filter
가 있는데, 이 또한 서블릿 표준에 해당이 됨자바에서는 서블릿 표준을 사용해서 웹 서비스 개발이 가능하다. 그럼 스프링을 안써도 되는가? → 스프링 문서를 보면 초기 J2EE 스펙이 복잡해서 스프링을 만들었음. 즉, 서블릿을 활용한 J2EE 만으로는 엔터프라이즈 급의 애플리케이션을 운영하기 어렵다. 따라서 스프링이 등장했다라고 보면된다.
스프링으로 웹 개발을 하면 되는데 서블릿은 몰라도 되는거 아닌가? → 다시 말하지만 스프링MVC 는 서블릿 기반으로 동작하기 때문에 서블릿이 어떻게 동작하는지 원리를 알아야 나중에 서비스 운영시에 서블릿 관련 이슈가 발생했을 때 대응하거나, 문제 원인을 찾아 해결할 수 있다. 따라서 기본적인 구조나 동작원리는 알고 있는게 좋다.
생명주기 메소드
jakarta.servlet
패키지에 속해있는 Servlet
인터페이스는 다음과 같다.
해당 인터페이스의 메소드 중에 getServletInfo()
나 getServletConfig()
메소드는 서블릿 정보나 서블릿 초기화 설정 값들을 설정할 때 쓴다고 보면 되는데, init(), service(), destroy()
생명주기 메소드만큼 중요하지는 않다. 즉, 이 3 메소드만 알아도 충분하다.
init(), service(), destroy() 메소드를 묶어서 라이프 사이클 메소드라고 부른다.
라이프 사이클이란?
→ 웹 컨테이너(톰켓이라고 생각하면됌)가 처음 실행 될 때, 서블릿 클래스(서블릿 클래스는 앞선 서블릿 인터페이스를 가지고 구현한 클래스 파일)를 로드를 한다.
서블릿 클래스 파일을 웹 컨테이너가 처음 실행될 때 불러오고, 이를 가지고 서블릿 객체를 만든다.(기본 생성자를 가지고). 이 때 init() 메소드를 실행시켜주는데, 웹 컨테이너가 시작할 때 한 번만(once) 실행한다.
그리고 나서 웹컨테이너는 클라이언트의 요청을 받아서 처리해주는 역할을 한다. 요청을 처리할 때 서블릿의 service() 메소드가 호출이된다.
자바 웹 개발자들이 서블릿을 구현할 때 service() 메소드에 처리하고 싶은 비즈니스 로직을 작성하면 된다. 그러면 요청을 받아서 응답을 반환해주는 작업을 진행하게 된다.
톰캣으로 서버를 운영하다가 보면 재배포를 해야할 때도 있는 등 서버를 종료해야할 때가 있는데 이 때 호출되는 메소드가 destroy() 메소드이다. 따라서 서블릿을 종료할 때 작업해주어야할 일이 있으면 destroy() 메소드에다가 작업을 해주면 톰켓 서버가 종료될 때 이 메소드가 호출이 된다.
서블릿과 서블릿 컨테이너가 HTTP 요청을 어떤 과정으로 처리하는지 살펴보자.
클라이언트로부터 사용자가 우리 서버에게 요청을 보냄(GET 메소드)
요청을 처음에 컨테이너가 받음. (톰캣을 생각하면 됌. 톰캣을 서블릿 컨테이너라고도 부르고, WAS 라고도 부름)
컨테이너가 요청을 받아서 요청을 처리해줄 적절한 서블릿을 찾아야한다.
예시는 서블릿 객체가 하나 뿐인데, 예를 들어 로그인을 처리하는 객체를 작성했으면 서블릿 클래스 파일을 가지고 웹 컨테이너가 서블릿 객체로 만들어 놓는다. (컨테이너 중에 로그인 처리하는 서블릿은 하나만 존재) 마찬가지로 로그아웃 하는 서블릿을 만들었다고 하면 해당 서블릿은 컨테이너 내에 하나만 존재한다.
즉, 서블릿은 웹 컨테이너에 하나씩만 인스턴스로 존재한다. 이를 싱글톤으로 생각할 수도 있는데, 싱글톤은 아니다. 왜냐하면 싱글톤 클래스 같은 경우에는 객체로 한 번밖에 못만들도록 내부에서 private 메소드로 생성자를 만드는데, 근데 서블릿은 이러한 구조는 아니다.
톰캣이 처음 실행될 때, 구조상 서블릿을 한 번만 실행한다 뿐이지 싱글톤까지는 아니다.
요청을 받았으면 그 요청을 가지고 두가지 객체를 만든다. request, response 객체를 만들어서 컨테이너가 서블릿을 관리하는데, 자신이 관리하는 서블릿 중에서 해당 HTTP 에서 URL 에 맞춰서 적절한 서블릿을 찾아서 해당 서블릿의 service() 메소드를 실행한다. 이 때, request 와 response 객체 만든 것을 파라미터로 전달.
그 객체 실행은 쓰레드 하나에서 실행된다.
우리 웹서버를 생각해보면 동시에 여러 사용자 요청을 처리할 수 있는데, 그것이 가능한 이유가 서블릿 객체는 하나지만, 서블릿 객체 하나에서 여러 쓰레드가 사용자의 요청을 처리해주는 식으로 동작되기 때문이다.
처리한 내용을 response 객체에 담아서 사용자에게 되돌려준다. 컨테이너에 돌려주면 컨테이너가 사용자에게 전달해주면서, 서블릿 객체는 톰캣 서버 유지하는 동안 살아있는 것이고, 요청을 처리하는 동안 사용했던 쓰레드는 반납하고 request, response 객체는 JVM 에게 반납한다.
서블릿 하나로 다수의 요청을 처리할 수 있다. 따라서 웹 서버나 WAS들은 기본적으로 멀티 쓰레딩으로 처리가 된다.
예를 들어, 클라이언트A가 보낸 요청을 서블릿 한 개에서 쓰레드 하나에 배정해서 작업을 처리해주고, 동시에 클라이언트B에서도 요청을 보내면 서블릿 하나에서 쓰레드 하나에 작업을 배정해서 요청을 처리하고 응답을 반환한다.
조심해야할 것은 서블릿으로 웹 서버를 구현할 때, 상태를 두면 안된다. (동시성 문제) 상태는 클래스 파일의 인스턴스 변수나 static 변수와 같은 것들이며, 이를 서블릿에서는 작성하면 안된다. (Thread-safe class로 작성해야한다.) -> 서블릿은 상태를 공유하기 때문에 위 변수들은 공유가 된다. 따라서 이 상태들에 다른 사용자에게 보여주면 안되는 정보를 담고있다고 하면 큰 문제가 될 수 있다.
앞선 Servlet 인터페이스와 마찬가지로 jakarta.servlet 패키지에는 다음과 같이 Filter 인터페이스가 존재한다.
웹서버 전반적으로, 공통적으로 적용할만한 처리들을 필터에서 적용시킬 수 있음.
필터에서 비즈니스적 처리를 하지는 않음.