← wiki

DispatcherServlet — HTTP 요청이 Controller까지 가는 길

@GetMapping("/orders/{id}")를 붙인 메서드를 하나 만들어 두면, 브라우저가 GET /orders/42를 보냈을 때 그 메서드가 실행된다. 심지어 42가 파라미터로 알아서 들어와 있다. 이 사이에서 도대체 무슨 일이 벌어지는가.

그 한가운데에 DispatcherServlet이 있다. 스프링 MVC의 심장이자, 들어오는 모든 HTTP 요청을 받아 "이건 어느 컨트롤러가 처리할 일이야" 를 판단해 넘기는 중앙 교통정리원이다. 이 글은 요청 하나가 이 관문을 통과해 내 컨트롤러 메서드에 닿고, 다시 응답이 되어 나가는 전 과정을 연다.

요청 수신DispatcherServlet
핸들러 찾기HandlerMapping
메서드 실행HandlerAdapter
응답 변환ViewResolver / 메시지 컨버터
Front Controller 패턴

DispatcherServlet은 모든 요청이 먼저 거치는 단일 입구다. 이렇게 앞단에 하나의 관문을 두고 뒤로 분배하는 구조를 프론트 컨트롤러 패턴이라 부른다. 공통 처리(로깅·인증 등)를 한 곳에 모을 수 있다는 게 핵심 이점이다.


1. 요청이 DispatcherServlet에 닿기까지

브라우저의 요청은 곧장 DispatcherServlet으로 오는 게 아니다. 그 앞에 웹 서버(서블릿 컨테이너, 예: 톰캣)가 있다.

  1. 1
    톰캣이 HTTP 요청을 받는다

    TCP로 들어온 바이트를 HttpServletRequest 객체로 파싱한다.

    스프링이 뜰 때 내장 톰캣이 함께 뜬다. 톰캣은 순수하게 "HTTP를 자바 객체로 바꿔 서블릿에 넘기는" 역할을 한다.

  2. 2
    모든 경로를 DispatcherServlet에 매핑

    보통 '/' 아래 전부를 이 하나의 서블릿이 받도록 등록돼 있다.

    그래서 /orders/users든 전부 일단 DispatcherServlet으로 모인다. 여기서부터 스프링 MVC의 영역이다.

서블릿이 뭔데

서블릿은 "HTTP 요청을 받아 응답을 만드는 자바 표준 객체"다. DispatcherServlet도 결국 하나의 서블릿이다 — 다만 직접 응답을 만들지 않고, 적절한 컨트롤러에게 위임하는 특별한 서블릿이다.


2. 핵심 5단계 — 요청에서 응답까지

DispatcherServlet이 요청을 받은 뒤 벌어지는 일은 다섯 단계로 요약된다. 이 순서가 스프링 MVC의 뼈대다.

DispatcherServletMVC 협력 객체들
① 핸들러 조회

HandlerMapping: URL로 컨트롤러 메서드 찾기

핸들러 + 인터셉터 반환

'이 요청은 OrderController.get()이야'

② 어댑터로 실행 위임

HandlerAdapter: 인자 바인딩 후 메서드 호출

③ 반환값 전달

객체(@RestController) 또는 뷰 이름

④ 응답 변환

메시지 컨버터(JSON) 또는 ViewResolver(HTML)

⑤ HTTP 응답 완성

상태코드·헤더·바디를 채워 반환

각 단계를 조금 더 풀어 보자.

  1. 1
    ① HandlerMapping — 누가 처리할지 찾기

    요청 URL·메서드(GET/POST)를 보고 담당 컨트롤러 메서드를 찾는다.

    @RequestMapping·@GetMapping 정보를 부팅 때 미리 색인해 두고, 들어온 요청과 매칭한다. 여기서 매칭 실패하면 곧 404다.

  2. 2
    ② HandlerAdapter — 메서드를 실제로 부르기

    찾은 메서드를 호출하되, 그 전에 파라미터를 만들어 넣는다.

    @PathVariable Long id에 URL의 42를, @RequestBody에 JSON 본문을 변환해 채운다. 이 "인자 만들어 넣기"를 담당하는 게 ArgumentResolver들이다.

  3. 3
    ③ 컨트롤러 실행 — 내 코드가 도는 지점

    드디어 내가 짠 @GetMapping 메서드 본문이 실행된다.

    여기서 반환하는 값이 무엇이냐에 따라 다음 단계가 갈린다. 객체를 반환하면 JSON으로, 문자열(뷰 이름)을 반환하면 HTML 렌더링으로.

  4. 4
    ④ 응답으로 변환 — 두 갈래

    @RestController면 메시지 컨버터가 JSON으로, 전통 MVC면 ViewResolver가 뷰를 찾는다.

    @ResponseBody/@RestController HttpMessageConverter가 객체를 JSON 문자열로 직렬화한다. 반면 뷰 이름을 반환하면 ViewResolver가 그에 맞는 템플릿(예: Thymeleaf)을 찾아 HTML을 그린다.

  5. 5
    ⑤ HTTP 응답 완성

    상태코드·헤더·바디를 채운 응답이 톰캣을 거쳐 브라우저로 돌아간다.


3. 왜 이렇게 여러 조각으로 나눴나

HandlerMapping, HandlerAdapter, ViewResolver, MessageConverter… 이름이 많다. 하지만 이렇게 쪼갠 데는 이유가 있다 — 각 단계를 독립적으로 교체·확장할 수 있게 하려는 것이다.

DispatcherServlet이 직접 다 하면
  • ·URL 매칭·인자 바인딩·직렬화가 한 덩어리
  • ·JSON 대신 XML을 쓰려면 본체를 수정
  • ·새 컨트롤러 방식 추가가 어렵다
  • ·테스트·확장이 경직된다
조각으로 나눴기에
  • ·각 단계가 인터페이스로 분리됨
  • ·메시지 컨버터만 갈면 응답 형식 교체
  • ·ArgumentResolver 추가로 커스텀 파라미터
  • ·DispatcherServlet은 '조율'만 담당
DispatcherServlet은 지휘자다

DispatcherServlet 자신은 URL 매칭도, JSON 변환도 직접 하지 않는다. 그저 "핸들러 찾아 → 실행시켜 → 응답으로 바꿔"라고 각 전문 조각에게 순서대로 시킬 뿐이다. 오케스트라의 지휘자처럼, 연주는 각 파트가 하고 지휘자는 흐름만 통제한다.


4. 컨테이너와의 연결

한 가지 짚어 둘 것. DispatcherServlet이 찾아 실행하는 컨트롤러들은 전부 스프링 컨테이너가 만들어 둔 빈이다. @RestController도 결국 @Component의 일종이라, 부팅 때 컨테이너에 등록된다.

  1. 1
    부팅

    컨테이너가 @Controller·@RestController를 빈으로 만든다.

  2. 2
    색인

    HandlerMapping이 그 빈들의 @RequestMapping을 훑어 URL↔메서드 표를 만든다.

  3. 3
    런타임

    요청이 오면 그 표에서 담당 빈의 메서드를 찾아 호출한다.

  4. 4
    AOP도 여기서

    컨트롤러·서비스가 @Transactional 프록시라면, 실행은 프록시를 거친다.

즉 "HTTP 요청 → 컨트롤러"의 길은 웹 계층(DispatcherServlet)컨테이너(빈으로 만들어진 컨트롤러) 가 맞물려 완성된다. 컨트롤러가 어떻게 빈이 되는지는 Bean 생성 과정, 그 컨트롤러/서비스에 트랜잭션이 어떻게 걸리는지는 AOP와 Dynamic Proxy로 이어진다.


한 장 요약

  1. 1
    정체

    모든 HTTP 요청이 먼저 거치는 단일 입구(프론트 컨트롤러). 스프링 MVC의 중앙 교통정리원.

  2. 2
    ① HandlerMapping

    URL·HTTP 메서드로 담당 컨트롤러 메서드를 찾는다. 실패하면 404.

  3. 3
    ② HandlerAdapter

    찾은 메서드를 호출하되 @PathVariable·@RequestBody 등 인자를 만들어 채운다.

  4. 4
    ③ 컨트롤러 실행

    내 @GetMapping 본문이 도는 지점. 반환값 종류가 다음 단계를 가른다.

  5. 5
    ④ 응답 변환

    객체 → JSON(MessageConverter) 또는 뷰 이름 → HTML(ViewResolver).

  6. 6
    설계 의도

    각 단계를 인터페이스로 쪼개 교체·확장 가능하게. DispatcherServlet은 조율만 한다.