[Spring] Dispatcher Servlet

Hood·2025년 6월 20일

Spring Boot

목록 보기
12/15
post-thumbnail

✍ Back-End 지식을 늘리자!

백엔드 개발자를 준비하면서 생긴 궁금증을 정리한 포스트입니다.


들어가기 전

Spring을 사용하면 기본적인 어노테이션만으로도 비교적 손쉽게 API를 만들 수 있습니다.
이것이 바로 스프링 프레임워크가 가진 편리함이라고 할 수 있습니다.

하지만 어노테이션을 사용해 코드를 작성하는 데 익숙해질수록,
“이 요청은 내부적으로 어떤 과정을 거쳐 처리될까?”라는 궁금증이 생기기도 합니다.

그래서 이번 포스트에서는 Spring MVC에서 요청을 가장 먼저 받아 처리하는 핵심 구성 요소인
DispatcherServlet을 중심으로 정리해보려고 합니다.


서블릿(Servlet)이란?

Servlet은 자바 웹 애플리케이션에서
클라이언트의 요청을 받아 처리하고, 그 결과를 응답으로 돌려주는 자바 객체입니다.

HTTP 요청을 처리하는 서블릿은 보통 HttpServlet을 상속받아 구현하며,
이 서블릿은 Servlet Container에 의해 생성되고 관리됩니다.

서블릿에는 대표적으로 다음과 같은 생명주기 메서드가 있습니다.

  • init() : 서블릿이 처음 생성될 때 호출되는 초기화 메서드
  • service() : 요청이 들어올 때마다 호출되어 실제 요청 처리를 담당하는 메서드
  • destroy() : 서블릿이 종료될 때 호출되어 자원을 정리하는 메서드

즉, 서블릿은 단순히 요청을 처리하는 클래스가 아니라,
컨테이너가 생명주기까지 관리해주는 웹 컴포넌트라고 볼 수 있습니다.


커스텀 서블릿 구현

직접 서블릿을 만들어 HTTP 메서드별 요청을 처리할 수도 있습니다.

// 커스텀 서블릿 정의
class MyServlet : HttpServlet() {

    override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
        resp.writer.write("GET 요청 처리됨")
    }

    override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
        resp.writer.write("POST 요청 처리됨")
    }

    override fun doPut(req: HttpServletRequest, resp: HttpServletResponse) {
        resp.writer.write("PUT 요청 처리됨")
    }

    override fun doDelete(req: HttpServletRequest, resp: HttpServletResponse) {
        resp.writer.write("DELETE 요청 처리됨")
    }
}

Spring Boot에서는 이런 서블릿도 다음과 같이 등록해서 사용할 수 있습니다.

@Configuration
class ServletConfig {

    @Bean
    fun myServletRegistration(): ServletRegistrationBean<MyServlet> {
        return ServletRegistrationBean(MyServlet(), "/myServlet/*")
    }
}

반면 Spring MVC에서는 직접 HttpServlet을 상속하기보다
보통 다음처럼 @RestController와 매핑 어노테이션을 사용해 요청을 처리합니다.

@RestController
@RequestMapping("/api")
class MyController {

    @GetMapping
    fun doGet() = "GET 요청 처리됨"

    @PostMapping
    fun doPost() = "POST 요청 처리됨"

    @PutMapping
    fun doPut() = "PUT 요청 처리됨"

    @DeleteMapping
    fun doDelete() = "DELETE 요청 처리됨"
}

즉, 스프링 MVC는 서블릿 기반 웹 개발을 더 편리하게 만들기 위해
추상화된 프로그래밍 모델을 제공한다고 이해하면 됩니다.


Servlet 동작 방식

사진 출처

서블릿의 요청 처리 흐름을 간단히 정리하면 다음과 같습니다.

  1. 클라이언트가 HTTP 요청을 보냅니다.
  2. 서블릿 컨테이너가 요청을 받아 HttpServletRequest, HttpServletResponse 객체를 준비합니다.
  3. 요청 URL에 맞는 서블릿을 찾습니다.
  4. 해당 서블릿의 service() 메서드를 호출합니다.
  5. service()는 요청 메서드에 따라 doGet(), doPost() 같은 메서드로 분기합니다.
  6. 서블릿이 응답을 생성하면, 컨테이너가 이를 클라이언트에게 반환합니다.

예전에는 배포 서술자인 web.xml을 통해 서블릿 매핑을 많이 설정했지만,
현재는 애노테이션과 자바 설정, 그리고 Spring Boot의 자동 설정을 더 많이 사용하는 편입니다.


서블릿 컨테이너란?

서블릿이 스스로 실행되는 것은 아닙니다.
서블릿을 생성하고 초기화하고 실행하고 종료까지 관리해주는 별도의 환경이 필요한데,
이 역할을 하는 것이 바로 서블릿 컨테이너(Servlet Container)입니다.

서블릿 컨테이너는 다음과 같은 역할을 합니다.

  • 클라이언트의 요청을 받아 서블릿에 전달
  • 요청과 응답 객체 생성
  • 서블릿 생명주기 관리
  • 응답 결과를 다시 클라이언트에게 반환

대표적인 예로 Tomcat이 있습니다.
Spring Boot에서는 내장 톰캣을 함께 사용하기 때문에,
별도로 웹 서버를 설치하지 않아도 애플리케이션을 실행할 수 있습니다.


웹 서버와 서블릿 컨테이너의 요청 처리 방식

사진 출처

서블릿 컨테이너 안에서 서블릿은 보통 다음과 같은 흐름으로 동작합니다.

  1. 웹 서버가 HTTP 요청을 받습니다.
  2. 요청을 서블릿 컨테이너로 전달합니다.
  3. 요청에 맞는 서블릿을 찾고, 없으면 로드합니다.
  4. 최초 실행 시 init()을 호출해 초기화합니다.
  5. 요청마다 service()를 호출해 요청을 처리합니다.
  6. 응답을 반환한 뒤, 서블릿은 이후 요청을 위해 계속 유지됩니다.
  7. 애플리케이션 종료 시 destroy()가 호출됩니다.

즉, 서블릿은 요청이 올 때마다 새로 만들어지는 것이 아니라,
한 번 생성된 뒤 여러 요청을 처리하는 방식으로 동작합니다.


디스패처 서블릿이란?

스프링 MVC를 공부하다 보면 가장 자주 듣게 되는 개념 중 하나가
바로 DispatcherServlet입니다.

DispatcherServlet은 Spring MVC의 핵심 서블릿으로,
들어오는 웹 요청을 가장 먼저 받아서 적절한 처리 흐름으로 보내주는 역할을 합니다.
이 때문에 흔히 Front Controller라고도 부릅니다.

즉, 모든 요청을 각 컨트롤러가 제각각 처리하는 것이 아니라,
먼저 DispatcherServlet이 요청을 공통으로 받고 필요한 컨트롤러에 위임하는 구조입니다.


Spring MVC를 간단히 보면

Spring MVC는 이름 그대로 Model, View, Controller 구조를 기반으로 동작합니다.

  • Model : 비즈니스 로직 처리 결과나 화면에 전달할 데이터
  • View : 사용자에게 보여줄 화면
  • Controller : 요청을 받고, 적절한 로직을 호출한 뒤, 결과를 반환하는 역할

그리고 이 구조의 맨 앞에서 전체 흐름을 조정하는 것이 바로 DispatcherServlet입니다.


DispatcherServlet의 동작 과정


사진 출처

Spring MVC에서 요청이 처리되는 흐름을 간단히 정리하면 다음과 같습니다.

  1. 클라이언트의 요청이 DispatcherServlet에 도착합니다.
  2. DispatcherServletHandlerMapping을 통해 어떤 컨트롤러가 이 요청을 처리할지 찾습니다.
  3. 찾은 핸들러를 실제로 실행하기 위해 HandlerAdapter를 사용합니다.
  4. 컨트롤러가 비즈니스 로직을 수행하고 결과를 반환합니다.
  5. 반환값이 뷰 이름이라면 ViewResolver가 이를 해석해 실제 뷰를 찾습니다.
  6. 모델 데이터가 뷰에 전달되고, 최종 응답이 생성됩니다.
  7. 생성된 응답이 클라이언트에게 반환됩니다.

즉, DispatcherServlet은 직접 모든 일을 처리하는 것이 아니라,
적절한 구성 요소를 찾아 요청 처리를 조율하는 중심 역할을 합니다.


DispatcherServlet의 역할

1. 클라이언트의 요청을 먼저 받아 공통 흐름을 처리합니다.

클라이언트 요청이 들어오면
DispatcherServlet이 가장 먼저 이를 받아 공통적인 웹 요청 처리 흐름을 시작합니다.

이 과정에서 요청은 HttpServletRequest, HttpServletResponse 객체를 기반으로 처리되며,
어떤 컨트롤러가 이 요청을 담당할지 HandlerMapping을 통해 찾습니다.

2. 세부 작업을 컨트롤러에 위임합니다.

적절한 컨트롤러를 찾은 뒤에는 HandlerAdapter를 통해
그 컨트롤러를 실행할 수 있는 형태로 연결합니다.

즉, 컨트롤러마다 구현 방식이 조금씩 달라도
DispatcherServlet은 어댑터를 이용해 일관된 방식으로 요청을 전달할 수 있습니다.

3. 뷰를 찾아 최종 응답을 만듭니다.

컨트롤러가 뷰 이름과 모델 데이터를 반환하면
ViewResolver가 실제 뷰를 찾고,
그 뷰가 모델 데이터를 활용해 최종 결과 화면을 생성합니다.

REST API처럼 JSON을 반환하는 경우에는
전통적인 뷰 렌더링 대신 HttpMessageConverter 등을 통해 응답 본문이 만들어집니다.

4. 정적 자원과 동적 요청을 구분해 처리합니다.

Spring MVC에서는 보통 먼저 요청 매핑을 확인하고,
적절한 컨트롤러가 없다면 정적 리소스 처리기로 넘겨
CSS, JS, 이미지 같은 정적 자원을 제공할 수 있습니다.

즉, 하나의 흐름 안에서 동적 요청과 정적 자원을 효율적으로 분리해 처리할 수 있습니다.


📌 결론

스프링 MVC의 편리함은 단순히 어노테이션을 쉽게 사용할 수 있다는 점에만 있는 것이 아닙니다.
그 내부에는 요청을 공통으로 받아 적절한 구성 요소에 위임하는 구조가 잘 설계되어 있습니다.

그 중심에 있는 것이 바로 DispatcherServlet입니다.
DispatcherServlet은 Spring MVC에서 프런트 컨트롤러 역할을 하며,
요청을 받아 컨트롤러 실행, 뷰 해석, 응답 생성까지 전체 흐름을 조율합니다.

즉, 스프링의 유연성과 확장성은
이러한 내부 구조 덕분에 가능하다고 볼 수 있습니다.

참고

  • Spring Framework 공식 문서
  • Jakarta Servlet 공식 문서
profile
달을 향해 쏴라, 빗나가도 별이 될 테니 👊

0개의 댓글