https://dcloud.tistory.com/178
지난 포스팅에서 동적 웹 페이지를 위해 서블릿이 도입된 배경과 서블릿의 장점을 살펴보았다. 그리고 서블릿을 재정의해서 서블릿의 요청을 처리하는 것도 알아보았다.
이번에는 이어서, 서블릿의 핵심 개념인 서블릿 컨테이너를 다루어보고 스프링에서 어떤 점을 개선했고 현재의 스프링 형태가 되었는지 살펴보자.
서블릿은 어떻게 관리되고, 서블릿은 어떻게 호출될까?
1. 서블릿 컨테이너
서블릿을 관리하기 위한 서블릿 컨테이너라는 것이 있다. 서블릿 컨테이너란 단어 그대로 서블릿을 담는 컨테이너이다.
우리가 자주 들어본 톰캣(Tomcat)이 대표적인 서블릿 컨테이너이자 WAS이다. 예를 들어, 사용자가 브라우저에서 HTTP 요청을 보내면 톰캣이 해당 요청을 받아들이고, 적절한 서블릿 인스턴스의 service() 메서드를 호출해 처리하게 된다.
톰캣은 서블릿 컨테이너로서 HTTP 요청을 받고, 서블릿을 실행해 동적인 웹 페이지를 생성한 후 그 결과를 클라이언트에게 반환한다. 여기서 WAS라는 용어는 웹 서버(정적 리소스 처리)와 서블릿 컨테이너(동적 리소스 처리)가 결합된 형태를 의미한다. 톰캣이 WAS로 불리는 이유는 서블릿과 JSP를 처리하는 컨테이너 역할을 수행하기 때문이다.
톰캣 자체가 서블릿 실행 환경을 제공한다고 생각하면 이해가 쉬울 것 같다.
서블릿 컨테이너의 기능
서블릿 컨테이너는 서블릿 객체의 생성, 초기화, 호출, 소멸까지의 인생주기를 관리하는데, 기본적으로 아래의 5가지 기능을 제공한다.
1) 네트워크 통신
2) 서블릿 객체 생명 주기 관리
3) 서블릿 객체 싱글톤으로 관리
4) JSP도 서블릿으로 변환 되어서 사용
5) 동시 요청을 위한 멀티 쓰레드 처리 지원
쉽게 말해 서블릿 컨테이너는 개발자가 웹 서버와 통신하기 위하여 소켓을 생성하고, 특정 포트 리스닝, 스트림을 생성하는 등의 복잡한 일들을 할 필요가 없게 해준다.
그리고 생명 주기를 관리한다는 것은 어렵게 생각할 것은 아니고, 서블릿 객체의 생성, 호출, 소멸을 서블릿 컨테이너가 해준다고 이해하면 쉽다.
여러 요청이 들어오면 멀티 스레드의 처리도 지원하는데 이 부분은 싱글톤과 함께 뒤에서 다시 다룰 것이다.
2. 서블릿 컨테이너의 요청 처리 과정
클라이언트에서 요청이 들어왔을 때, 서블릿 컨테이너가 어떤 역할을 하는지 살펴보자.
웹 서버에서 사용자 요청이 들어오면 서블릿 컨테이너는 요청과 매핑된 서블릿을 찾게 된다. 좀 더 세부적인 과정은 아래와 같다.
1) 사용자가 URL을 클릭하면 HTTP Request를 Servlet Container에 보낸다.
2) Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성한다.
3) 사용자가 요청한 URL을 분석하여 어느 서블릿에 대한 요청인지 찾는다. (web.xml이 어느 서블릿에 대해 요청한 것인지 탐색)
4) 컨테이너는 서블릿 service()메소드를 호출한다. POST/GET 여부에 따라 doGet() 또는 doPost()가 호출된다.
5) doGet()이나 doPost() 메소드는 동적인 페이지를 생성한 후 HttpServletResponse 객체에 응답을 보낸다.
6) 응답이 완료되면 HttpServletRequest, HttpServletResponse 두 객체를 소멸시킨다.
위 과정은 이전 포스팅에서 다루었다. 여기서 추가로 web.xml이 어떤식으로 생겼는지 확인해보면 이해가 쉬울 것 같다.
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.test.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
위 설정 파일은 /hello 경로로 요청이 들어오면 HelloServlet으로 처리하겠다를 명시한다. 그리고 HelloServlet은com.test.HelloServlet 위치에 있어요 라고 명시해놓는다. 이것을 통해 사용자의 URL과 서블릿을 매핑할 수 있게 된다.
그래서 요청이 들어오면 서블릿 인스턴스를 먼저 확인하고, 있다면 그대로 가져다쓰고 없다면 새로 생성을 하게 된다. 생성을 하게되면 지난 포스팅에서 다루었던 init() 메서드를 호출하고 스레드를 생성하게 된다. 그리고 여기에 HttpServletRequest, HttpServletResponse 객체를 인자로 전달한다.
+ 만약 여러 요청이 들어오게 되면 어떻게 동작할까?
위에서 서블릿 컨테이너의 특징에서 눈치챘을 수도 있겠지만, 여러 요청이 들어오면 멀티스레드로 각 요청을 처리한다.
사용자로부터 요청을 받을 때마다 요청을 처리할 스레드를 생성한다.
요청을 받을 때마다 스레드를 생성하는 것은 굉장히 비효율적이지 않을까? 그래서 아래의 두 개념이 등장한다!
싱글톤과 스레드풀
바로 싱글톤과 스레드 풀이다. 두 가지 모두 서블릿 메모리의 효율성을 위한 장치이다.
첫째로 싱글톤은, 서블릿을 하나의 인스턴스로만 관리한다는 것이다. 서블릿 객체는 한 번만 생성되고 그 이후에는 동일한 인스턴스가 재사용된다. 요청이 있을 때마다 새로운 서블릿 객체를 생성하는 것은 메모리와 자원을 낭비하기 때문이다.
그리고 스레드풀(Thread Pool)은 클라이언트로부터 요청이 들어올 때마다 스레드를 새로 생성하지 않고, 미리 생성된 스레드를 재사용하는 것이다.
미리 생성된 스레드를 재사용해 요청이 발생할 때마다 스레드를 만들어내는 비용을 줄이고, 일정한 수의 스레드로 효율적으로 요청을 처리한다.
톰캣은 기본적으로 200개의 스레드를 풀에 생성해놓고, 요청이 들어오면 그 중 하나를 사용하여 서블릿을 처리한다.
즉 최초 로딩이 될 때 서블릿 객체를 미리 만들어두고, 재활용하여 모든 요청이 동일한 서블릿 인스턴스에 접근하게 된다.
3. 스프링의 서블릿 관리
요청당 서블릿을 정의한다면 비효율적이지 않을까?
다시 돌아와, 요청당 생성되는 서블릿을 생각해보자.
위 내용을 정리해보면, 서블릿은 하나의 요청을 담당하는 것을 볼 수 있다. 서블릿은 요청을 받아 파싱처리를 하고, 그에 맞는 핸들러를 찾고, 핸들러를 호출하고 응답을 한다.
이걸 그림으로 그려보면 아래와 같을 것이다.
한 눈에 봐도 중복된 부분이 많은 것을 볼 수 있다.
프론트 컨트롤러 패턴의 등장
그래서 Spring MVC는 이런 공통 로직 처리를 위해 Front Controller 패턴을 사용한다. 앞단에 전면 컨트롤러를 두는 것이다.
서블릿을 하나만 두고 모든 요청을 처리할 수 있도록 한다.
그래서 이전에는 각 요청마다 개별 서블릿이 요청을 직접 처리했지만, 스프링에서는 디스패처 서블릿이 모든 요청의 진입점이 된다.
디스패처 서블릿이 그 요청을 적절한 핸들러로 분배하는 역할을 맡게 되었다.
오해 주의!
여기서 서블릿이 하나라고 해서 모든 요청을 처리하는 스레드가 하나라는 뜻은 아니다!
마찬가지로 멀티스레드 환경에서 동작하는 것이고, 하나의 디스패처 서블릿이 여러 요청을 처리할 수 있지만, 각 요청은 개별 스레드에서 처리되게 된다.
이해를 위해 다시 요청 처리 흐름을 정리하자면,
1. 클라이언트 요청이 들어오면, 이 요청은 톰캣 같은 서블릿 컨테이너가 받는다.
2. 서블릿 컨테이너는 각 요청에 대해 새로운 스레드를 생성하거나, 스레드 풀에서 스레드를 가져와 요청을 처리한다.
3. 스레드가 디스패처 서블릿에 요청을 전달한다.
4. 디스패처 서블릿은 요청을 분석하고 적절한 컨트롤러를 찾아 해당 로직을 실행한다.
5. 요청이 처리되고 나면, 해당 스레드는 응답을 보내고 다시 스레드 풀로 돌아가거나 종료된다.
즉, 디스패처 서블릿이 하나라는 것은 요청을 중앙에서 분배하고 처리하는 역할을 하는 서블릿이 하나라는 의미라는 것을 명심하자!
조금 더 역할 분리해보기
위 그림에서도 아직 역할이 모여있는 것을 볼 수 있다. 이를 View, ViewResolver, HandlerMapping 등으로 분리한 것이 우리가 흔히 봤던 스프링의 패턴이다.
참고자료
https://www.youtube.com/watch?v=calGCwG_B4Y
https://velog.io/@jihoson94/Servlet-Container-%EC%A0%95%EB%A6%AC
https://velog.io/@mooh2jj/WAS%EB%8A%94-%EC%84%9C%EB%B8%94%EB%A6%BF-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0
https://velog.io/@falling_star3/Tomcat-%EC%84%9C%EB%B8%94%EB%A6%BFServlet%EC%9D%B4%EB%9E%80
'Backend > Spring' 카테고리의 다른 글
[Spring] 스프링으로 서블릿을 어떻게 다룰까? - (1) 서블릿 (0) | 2024.03.06 |
---|