✏️ Servlet 동작 원리 With DispatcherServlet

박상민·5일 전
1

개념 정리!

목록 보기
22/22

Servlet, 왜 알아야 할까?

Java 백엔드 개발자는 대부분 Spring Framework를 사용한다. 그중에서도 Spring MVC, Spring Boot를 사용해 개발하는데, 이 모든 프레임워크의 핵심에는 공통된 기반 기술인 Servlet이 존재한다.

서블릿은 HTTP 요청-응답 흐름을 처리하기 위한 자바 표준 인터페이스이다.
Spring MVC의 @Controller, @RestController도 결국 내부적으로는 DispatcherServlet이라는 서블릿 위에서 동작한다.

왜 알아야 할까?

  1. Spring의 내부 동작 원리를 이해할 수 있다.
  • 필터, 인터셉터, DispatcherServlet, HandlerMapping, ViewResolver 등 Spring MVC 구성 요소는 서블릿 기반 위에서 작동한다.
  • Servlet 흐름을 이해하지 않고, Spring만 사용하면 내부 동작은 블랙 박스가 된다.
  1. 커스터마이징이 필요한 경우가 존재한다.
  • 필터 체인, 비동기 서블릿, WebSocket 처리 등 Spring이 추상화하지 않은 서블릿 API를 직접 다뤄야 하는 경우가 존재한다.
  1. 서블릿은 자바 웹의 표준이다.
  • 톰캣 같은 웹 서버는 모두 서블릿 스펙을 기준으로 설계되어 있다.
  • 톰캣의 구조, 요청 처리 흐름을 이해하려면 결국 서블릿 개념을 이해해야 한다.

Servlet이란? 개념과 구조 이해하기

서블릿: Java를 사용해 동적인 웹 페이지를 생성할 수 있도록 해주는 서버 사이드 웹 프로그래밍 기술

  • 웹 요청과 응답의 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 한다.
  • 클라이언트의 HTTP 요청을 처리하고, HTTP 응답을 생성하는 역할을 수행

예를 들면, 사용자가 로그인 시 아이디/비밀번호를 입력해 서버로 전송하면 서블릿은 해당 요청을 받아 DB와 비교하여 인증 처리를 수행한다. 그에 따른 적절한 응답을 클라이언트에 전달한다.

핵심 기능과 특징

  • 웹 요청/응답의 흐름을 추상화한 API를 제공해서 HTTP 프로토콜의 처리를 간단한 메서드 호출로 다룰 수 있게 한다.
  • 서블릿은 웹 서버(서블릿 컨테이너) 내에서 실행되며, 클라이언트 요청이 서버에 들어오면 이를 가로채서 처리하고 응답을 반환한다.
  • 응답 결과는 HTML, JSON 등 다양한 형태로 생성이 가능하며 동적인 웹 콘텐츠 생성의 기반이 된다.

서블릿 생명주기 (Servlet Lifecycle)

서블릿은 자바 클래스로 작성되며, 서블릿 컨테이너(ex. 톰캣)에 의해 관리한다.
컨테이너는 서블릿의 생성부터 요청 처리, 종료까지의 전체 과정을 자동으로 관리한다. 이를 서블릿의 생명주기라고 부른다.

서블릿 생명주기 메서드

  • 생명주기의 각 단계에서 호출되는 메서드

생명주기 흐름

  1. 초기화(init)
    클라이언트의 요청이 처음 들어오면, 컨테이너는 해당 요청을 처리할 서블릿이 메모리에 존재하는지 확인한다. 없다면, 서블릿 객체를 생성한 뒤 init() 메서드를 호출하여 한 번만 초기화 작업을 수행한다.
  • init()서블릿당 1번만 호출된다.
  • 주의: 서블릿 load-on-startup 설정에 따라 초기화 시점이 달라진다..
    • 기본값은 최초 요청 시점에 초기화
    • load-on-startup 값을 설정하면 웹 애플리케이션이 시작될 때 즉시 초기화
  1. 요청 처리 (service -> doGet/doPost)
    init() 이후 클라이언트의 요청이 들어올 때마다 service() 메서드가 실행된다.
    service() 메서드는 HTTP 요청 방식(GET, POST 등)에 따라 doGet() or doPost()로 나뉜다.
  • 이때, 서블릿 컨테이너는 요청 정보를 담은 HttpServletRequest, 응답 출력을 위한 HttpServletResponse 객체를 생성하여 서블릿에 전달한다.
    • 두 객체는 요청이 처리되는 동안에만 유효하며, 요청 처리가 끝나면 컨테이너가 자동으로 소멸시킨다.
    • 매 요청마다 새로운 객체가 생성되므로, 스레드 간 공유는 불가능하며 반드시 메서드 내 지역 변수로 사용해야 한다.
  1. 종료 처리 (destroy)
    서버가 종료되거나 서블릿이 메모리에서 언로드될 경우 destroy() 메서드가 호출된다.
    destroy()한 번만 호출되며, 리소스 정리나 로그 저장 등 종료 작업을 수행할 수 있다.

서블릿은 기본적으로 싱글톤으로 관리된다.
따라서 init(), destroy()는 한 번씩만 호출되고doGet(), doPost()는 매 요청마다 호출된다.

요청마다 생성되는 HttpServletRequest, HttpServlerResponse 객체는 요청 처리 후 컨테이너에 의해 자동으로 정리된다.

서블릿 매핑과 URL 처리 방식: 기본 흐름

  1. 사용자가 웹 브라우저에 URL을 입력하거나 링크를 클릭하면, 해당 요청은 HTTP Request 형태로 서블릿 컨테이너로 전달
  2. 서블릿 컨테이너는 요청을 받으면 HttpServletRequest, HttpServletResponse 객체를 생성
  • 이 객체들은 요청 정보를 담고 있으며, 서블릿에 전달되어 요청 처리에 사용된다.
  1. 컨테이너는 web.xml 설정 또는 @WebServlet 어노테이션을 기준으로, 요청된 URL 패턴이 어떤 서블릿과 매핑되는지 확인
  2. 매핑된 서블릿이 있으면, 컨테이너는 해당 서블릿의 service() 메서드를 호출하고, HTTP 메서드 (GET, POST 등)에 따라 doGet(), doPost() 메서드로 분기된다.
  3. doGet(), doPost()에서 요청에 대한 로직을 처리하고 결과 데이터를 HttpServletResponse 객체를 통해 응답으로 전송한다.
  4. 응답이 완료되면, 요청 처리에 사용된 HttpServletRequest, HttpServletResponse 객체는 소멸된다.

요약

사용자의 HTTP 요청은 서블릿 컨테이너에 의해 URL 메핑 -> 서블릿 실행 -> 응답 처리의 흐름으로 이어지며, 이 과정은 요청마다 새로운 HttpServletRequest/HttpServletResponse 객체가 생성되어 처리된다.

서블릿 매핑과 URL 처리 방식: 실제 톰캣 내부 동작 기반

실제 내부 동작 기반 설명

  1. 사용자 요청 발생
GET http://localhost:8080/login
  • 해당 요청은 OS -> 네트워크 스택을 거쳐 톰캣 서버의 8080 포트로 전달
  1. 톰캣이 요청을 수신
  • HTTP Connector가 8080 포트에서 요청을 수신
  • Request 객체(RequestFacade)와 Response 객체(ResponseFacade)를 생성
  • 이 객체들은 각각 HttpServletRequest, HttpServletResponse 인터페이스의 구현체이며, 서블릿에 전달되기 전 내부적으로 설정
  1. 서블릿 객체 로딩 & 초기화
  • 해당 서블릿이 최초 요청이라면 다음 순서로 실행
//서블릿 객체가 없다면 newInstance()
Servlet servlet = new LoginServlet();

//init() 호출
servlet.init()
  • 이후 서블릿 인스턴스는 싱글톤으로 메모리에 유지
  1. 요청 처리 단계
servlet.service(request, response);

-------
// service 메서드 내부 동작
public void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    
    String method = req.getMethod(); // GET or POST

    if (method.equals("GET")) {
        doGet(req, resp);
    } else if (method.equals("POST")) {
        doPost(req, resp);
    }
}
  • doGet() 또는 doPost()에서는 요청에 따른 비즈니스 로직을 수행하고, response.getWriter().write(...) 등을 통해 HTML, JSON 등의 응답 데이터를 작성한다.
  1. 응답 후 정리
  • 응답이 완료되면 톰캣은 내부적으로 Request, Response 객체의 연결을 해제하고, 가비지 컬렉션 대상이 되도록 처리 후 소멸

전체 흐름 요약

[브라우저] 
    ↓ 요청
[Coyote HTTP Connector] 
    ↓
[Engine → Host → Context] 
    ↓
[URL 매핑: Wrapper 선택 (Servlet 매핑)]
    ↓
[서블릿 service() 실행]
    ↓
[doGet()/doPost() → 응답 생성]
    ↓
[Response 전송 → 객체 정리]

DispatcherServlet: Spring MVC와 서블릿의 연결고리

Spring MVC는 단순히 @Controller를 사용하는 추상화된 프레임워크처럼 보이지만, 핵심에는 DispatcherServlet이 존재한다.

Spring은 web.xml 혹은 Java Config를 통해 DispatcherServlet을 서블릿으로 등록하고, 이 서블릿이 모든 HTTP 요청을 받아서
핸들러 매핑 -> 인터셉터 -> 컨트롤러 -> 뷰 처리로 이어지는 전체 요청 흐름을 담당한다.

Spring MVC에서 클라이언트의 요청을 실제로 받아 처리하는 핵심 컴포넌트는 바로 DispatcherServlet이다.
DispatcherServlet은 Spring이 제공하는 특수한 서블릿으로, 모든 요청을 중앙에서 받아 분기하고 흐름을 제어하는 역할을 한다.

DispatcherServlet = 요청의 진입점이자 전체 흐름의 조율자

DispatcherServelt의 동작 흐름

[요청]
   ↓
DispatcherServlet
   ↓
HandlerMapping (어떤 Controller인지 찾기)
   ↓
HandlerAdapter (어떻게 호출할지 결정)
   ↓
Controller (@Controller 클래스의 메서드 실행)
   ↓
ModelAndView 생성
   ↓
ViewResolver (어떤 뷰를 렌더링할지 찾기)
   ↓
View (JSP, Thymeleaf 등 렌더링)
   ↓
[응답]

서블릿과 DispatcherServlet 비교

구분서블릿 (Servlet)DispatcherServlet (Spring MVC)
요청 수신서블릿 컨테이너가 직접 요청 처리서블릿 컨테이너는 DispatcherServlet에 요청 전달
매핑 방식web.xml 또는 @WebServletweb.xml, @Configuration, WebApplicationInitializer
요청 처리service()doGet() / doPost()doDispatch() → Controller 메서드 호출
파라미터 처리request.getParameter() 직접 호출@RequestParam, @RequestBody 등으로 자동 바인딩
응답 처리response.getWriter().write(...) 직접 작성ModelAndView, ResponseEntity, ViewResolver 등으로 처리
확장성구조가 단순하나 수동으로 구현해야 함인터셉터, 예외 처리, 어댑터 등 다양한 컴포넌트로 유연하게 확장

DispatcherServlet 내부 동작
DispatcherServlet은 HttpServlet을 상속한 서블릿 그 자체이다.

/* DispatcherServlet */
public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) {
        doDispatch(request, response);
    }
}

---

/* DoDispatch() 내부 작업 */
// 요청을 처리할 핸들러 찾기
HandlerExecutionChain handler = getHandler(request);

// 핸들러 어댑터 결정
HandlerAdapter adapter = getHandlerAdapter(handler.getHandler());

// 컨트롤러 메서드 호출
ModelAndView mv = adapter.handle(request, response, handler.getHandler());

// 뷰 이름 → ViewResolver 통해 View 객체로 변환
View view = resolveViewName(mv.getViewName());

// View 렌더링
view.render(mv.getModel(), request, response);

Spring은 서블릿의 기본 구조는 유지하면서, 그 위에 컴포넌트를 층으로 쌓아 추상화하고 확장한 구조이다.

DispatcherServlet 등록 방식: Spring Boot

@Configuration
public class WebConfig {
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }
}

Spring Boot에서는 기본적으로 경로에 DispatcherServlet이 등록되어 있어서 모든 요청이 DispatcherServlet을 거쳐 들어오도록 설정되어 있다.

DispatcherServlet 역시 서블릿 컨테이너인 톰캣 위에서 동작하며, 톰캣은 요청을 수신하고 서블릿을 실행하는 기반 환경을 제공한다.

정리

DispatcherServlet은 서블릿 위에 구축된 Spring MVC 아키텍처의 중심축이다.
서블릿 생명주기와 매핑 구조를 그대로 따르면서,
요청 처리 흐름을 분리하고 확장할 수 있는 핵심 컴포넌트 체계를 제공한다.

실제 흐름 정리

지금까지 알아본 핵심 구조

  • Serlvet이란 무엇이며 어떻게 작동하는가
  • DispatcherServlet이 Spring MVC에서 어떤 역할을 수행하는가
  • 서블릿 생명주기와 요청 처리 흐름
  • 요청 -> 컨트롤러 -> 응답까지의 전체 프로세스

이 흐름을 하나의 시퀀스로 통합해보자
전체 요청 처리 흐름

[사용자 (브라우저)]
    ↓ HTTP 요청 (GET /login)
[TCP/IP, 네트워크 스택]
    ↓
[서블릿 컨테이너 (톰캣)]
    ↓
[DispatcherServlet (Spring)]
    ↓
[HandlerMapping] → 해당 컨트롤러 찾기
    ↓
[HandlerAdapter] → 호출 방법 결정
    ↓
[@Controller 메서드 실행]
    ↓
[ModelAndView or @ResponseBody 응답 생성]
    ↓
[ViewResolver → View 렌더링]
    ↓
[HttpServletResponse에 응답 작성]
    ↓
[사용자에게 HTTP 응답 반환]

실제 코드 흐름

// DispatcherServlet → doDispatch()
HandlerExecutionChain handler = getHandler(request); // 요청을 처리할 핸들러 찾기
HandlerAdapter adapter = getHandlerAdapter(handler.getHandler()); // 핸들러 어댑터 호출
ModelAndView mv = adapter.handle(request, response, handler.getHandler()); // 실행
View view = resolveViewName(mv.getViewName()); // 뷰 찾기
view.render(mv.getModel(), request, response); // 렌더링

정리

지금까지 내용을 통해
HTTP 요청 -> DispatcherServlet -> 핸들러 실행 -> 응답 전송에 이르는
전체 Spring MVC 요청 흐름을 구조적으로 이해했다.

🏁 마무리: 왜 서블릿을 알아야 했는가?

Spring MVC는 매우 잘 추상화된 프레임워크다.
덕분에 @Controller, @RestController, @Component 같은 어노테이션만으로도 쉽게 웹 애플리케이션을 만들 수 있다.

하지만 편리함의 이면에는 복잡한 구조와 흐름이 존재하고, 그 중심에는 서블릿과 DispatcherServlet이 존재한다.

이번 정리를 통해 얻은 핵심 인사이트

  • 서블릿은 자바 웹 기술의 시작점이자, 모든 웹 요청을 처리하는 표준 컴포넌트이다.
  • DispatcherServlet은 Spring이 제공하는 서블릿 확장체로, 요청을 받고 처리 흐름을 조율하는 중심축이다.
  • Spring의 모든 요청 처리 과정은 결국 서블릿 컨테이너 위에서 동작하며, 내부적으로는 서블릿 생명주기와 URL 매핑, HTTP 요청/응답 처리 로직이 녹아 있다.

출처
https://docs.oracle.com/javaee/6/tutorial/doc/bnafd.html
https://www.oracle.com/java/technologies/java-servlet-tec.html
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet.html

0개의 댓글

관련 채용 정보