https://www.youtube.com/watch?v=PH8-V6ah0XQ&list=LL&index=1
IT 늦공 김부장 - [Servlet - Jsp - MVC - Spring 진화과정에 대한 리뷰]
위 영상을 보고 서블릿부터 JSP, 스프링으로 이어지는 웹 변천사를 한눈에 이해할 수 있었다. 그리고 눈에 띄는 부분은 마지막 25분 32초 였다.
해당 부분에서는 Was의 구조와 개발 방법의 관계를 주제로, 서블릿과 JSP, 스프링의 WAR 파일 형식을 비교하는데 세 가지 방법의 WAR 파일이 모두 똑같은 형식을 유지하는 것을 알 수 있다.
이것은 사실 당연한 이유일 수도 있는데, 서블릿에서 스프링까지의 변천사가 기존의 기술을 조금씩 확장한 것이기 때문이다. 둘다 자바 기반의 서블릿 컨테이너에서 동작하고, 웹 요청과 응답을 처리하는 방식이 매우 유사하다.
다시 말해, 스프링도 내부적으로 서블릿을 확장한 구조이고 서블릿을 사용하는 복잡한 작업을 추상화해 더 편리하게 애플리케이션을 개발할 수 있도록 한 것이다.
1. 서블릿이란?
우선 서블릿은 무엇일까? 간단히 말해 서블릿은 자바를 사용해 동적인 웹을 만들기 위해 필요한 기술이다.
서블릿 : 클라이언트의 요청을 처리하고, 그 결과를 반환하는 Servlet 클래스의 구현 규칙을 지킨 자바 웹 프로그래밍 기술
서블릿은 객체인가 API인가
여러 포스팅을 보니, 이 부분을 제대로 설명하지 못하고 넘어가는 글을 많이 보았다. 결론부터 말하면 두 가지 모두를 말한다.
우선 서블릿 자체의 개념은, 자바 기반의 웹 어플리케이션에서 클라이언트로부터 오는 HTTP 요청을 처리하고, 그에 대한 응답을 생성하는 자바 객체를 말한다.
그리고 동시에 서블릿 API라는 규격을 말할 때에도 사용된다. 자바에서 동적인 웹 어플리케이션을 만들기 위한 규칙과 인터페이스를 말한다. HTTPServlet 클래스가 가장 대표적인 구현체이다.
즉 정리하자면, 서블릿이란 HTTP 요청을 처리하는 객체를 말하는 동시에, 이러한 객체를 만들 수 있는 기술과 인터페이스를 지칭하기도 하는 것이다.
동적인 페이지와 서블릿
위 영상에서도 나오지만, 가장 처음 웹 서버는 클라이언트의 요청을 정적인 페이지로만 응답할 수 있었다. 그런데 정적인 페이지로만 응답해주기에는 한계가 있어 동적인 페이지가 요구 되었고, 동적인 페이지를 제공하기 위해서는 웹 서버가 다른 곳에 도움을 요청해 동적인 페이지를 작성해야했다. 여기서 바로 서블릿이 웹서버가 동적인 페이지를 제공할 수 있도록 도와준다.
위에서 HTTP 요청을 처리하는 것이 서블릿이라고 했는데, 처음 웹을 접하는 입장에서는 "그래서 이게 왜 동적인 페이지와 관계가 있는건데??" 와 같은 의문이 들 수 있다.
아주 간단하게 생각해보면, 만약 브라우저에서 데이터베이스에 있는 데이터를 가져와달라고 HTTP 요청을 보냈다고 해보자. 여기서 HTTP 응답은 누가 해줘야할까? HTTP 요청을 처리할 누군가가 있어야한다. 그 역할을 해주는 것이 서블릿이다.
서블릿은 HTTP 요청을 하나씩 처리하는 객체이면서, 동시에 동적인 웹 페이지를 생성한다. 예를 들어, 클라이언트가 상품 목록을 요청하면, 서블릿은 데이터베이스에서 상품 데이터를 가져와 그 결과를 HTML로 렌더링해 클라이언트에게 반환한다.
이처럼 서블릿은 정적인 웹 서버에서 할 수 없는, 동적인 응답을 생성하기 위한 핵심 기술이다. 서블릿은 동적 페이지를 만들기 위한 백엔드 로직을 처리하는 곳으로 생각하면 이해가 쉬울 것이다.
서블릿과 HTTP
서블릿이 동적인 웹에 왜 필요한지는 충분히 이해가 되었을 것 같다.
서블릿은 동적인 웹을 만들어주는 프로그램이라는 것 외에도, 서블릿은 웹 요청과 응답의 흐름을 간단한 메서드 호출로 체계적으로 다룰 수 있게 해준다.
위 그림을 보면, HTTP 요청과 응답이 굉장히 복잡하게 오고 가는 것을 볼 수 있다. 개발자들이 이 요청과 응답을 일일이 파싱해서 처리하거나 하나하나 응답을 만들어주어야 한다면, 매우 번거로운 작업을 반복하게 될 것이다.
서블릿은 이것을 굉장히 간단하게 처리할 수 있다. HttpServletRequest라는 인터페이스를 사용해 처리하기 때문이다.
해당 인터페이스는 IDE에서도 확인할 수 있고, 위의 깃 링크에서도 해당 코드를 확인할 수 있다.
다시 돌아와, 만약 이런 GET 요청이 들어왔다고 가정해보자. 개발자가 일일이 이런 요청을 하나하나 파싱을 해야할까?
개발자는 서블릿의 HttpServletRequest.getMethod(); 메서드를 호출하기만 하면 아주 쉽게 정보를 얻을 수 있다. 이외에도 HttpServletRequest를 호출해 처리할 수 있는 메서드들은 위의 깃 링크에서 확인할 수 있다.
여기까지 보면 서블릿을 통해 HTTP 요청이 간단히 처리된다는 사실은 이해할 수 있을 것 같다. 하지만 구체적으로 잘 와닿지는 않는다.
정확히 HTTP 요청이 어떤식으로 처리된다는 것일까?
서블릿 동작 과정
서블릿 동작 과정의 전반적인 흐름은 아래와 같다.
1. 클라이언트가 웹 서버에 요청을 보낸다.
2. WAS(톰캣 등)가 요청을 받아 서블릿에 전달한다.
3. WAS는 서블릿을 찾아 요청을 처리할 준비를 한다.
4. 서블릿의 service() 메서드가 호출되어 요청에 맞는 메서드(doGet, doPost 등)를 실행한다.
5. 서블릿은 요청을 처리하고, 결과를 HTTP 응답으로 클라이언트에게 보낸다.
하지만 아직도 잘 와닿지 않는다. 실제 코드를 뜯어보자.
서블릿 코드 뜯어보기
public class MyServlet extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
super.init();
}
public void destroy() {
super.init();
}
public void service(HttpServletRequest request, HttpServletResponse response) {
super.service();
}
}
우선 요청을 처리할 서블릿 객체는 위처럼 생겼다.
위에서도 언급했지만, 서블릿은 자바 웹 애플리케이션에서 HTTP 요청을 처리하는 기본 객체이자 단위이다. 그리고 서블릿의 생명주기는 코드에서 보이듯이 초기화(init) → 요청 처리(service) → 종료(destroy)의 세 단계로 이루어진다.
각 단계를 간단히 살펴보면 아래와 같다.
1. 초기화 단계
서블릿이 처음으로 클라이언트 요청을 받으면, 서블릿 컨테이너는 해당 서블릿 객체가 메모리에 존재하는지 확인한다.
만약 서블릿 객체가 존재하지 않으면, 컨테이너는 서블릿을 메모리에 로드하고 초기화 작업을 수행하는데 이때 호출되는 메서드가 init() 이다.
이 메서드는 서블릿이 한 번만 호출되게 하고, 서블릿의 초기화 작업을 처리한다.( 데이터베이스 연결 설정이나 전역 변수 초기화 등)
2. 요청 처리 단계 (service)
초기화가 완료된 서블릿은 클라이언트로부터 HTTP 요청을 받는다. 요청이 들어올 때마다 서블릿의 service() 메서드가 호출된다.
service() 메서드는 HTTP 요청 메서드(GET, POST 등)에 따라 적절한 doGet() 또는 doPost() 메서드를 호출하여 요청을 처리하게 된다.
즉, service() 메서드가 요청을 처리하는 중심 역할을 한다. 실제 비즈니스 로직이나 데이터를 처리하는 것은 doGet()이나 doPost()에서 이루어진다.
실제 서비스 코드를 확인하면 바로 이해가 갈 것이다. 서비스 메서드는 아래와 같다.
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
...
this.doGet(req, resp);
...
} else if (method.equals("HEAD")) {
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
3.종료 단계 (destroy)
서블릿이 더 이상 필요하지 않거나 서버가 종료될 때, destroy() 메서드가 호출되어 서블릿의 자원을 해제하고, 마무리 작업(데이터베이스 연결 해제, 각종 핸들러를 닫기)을 처리한다. 서블릿의 생명주기에서 마지막으로 호출되면서 서블릿은 메모리에서 해제된다.
그래서 만약 본인이 정의한 서블릿을 원하는 대로 동작시키려면 아래처럼 재정의를 통해 원하는 동작을 실행시킬 수 있다.
@WebServlet("/resources")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// HttpServletRequest와 HttpServletResponse를 사용해서 재정의
}
}
생각보다 서블릿을 정리하는 포스팅이 길어졌다. 서블릿 컨테이너에 대한 내용은 바로 다음 포스팅에서 이어서 다루어보겠다.
참고자료
https://www.youtube.com/watch?v=calGCwG_B4Y&t=32s
https://www.youtube.com/watch?v=PH8-V6ah0XQ&list=LL&index=1
https://velog.io/@falling_star3/Tomcat-%EC%84%9C%EB%B8%94%EB%A6%BFServlet%EC%9D%B4%EB%9E%80
'Backend > Spring' 카테고리의 다른 글
[Spring] 스프링으로 서블릿을 어떻게 다룰까? - (2) 서블릿 컨테이너와 Dispatcher Servlet의 도입 (0) | 2024.03.10 |
---|