[Spring] Spring MVC — 요청이 들어오면 Spring 내부에서 무슨 일이 벌어질까

Raha·2026년 3월 21일

Spring

목록 보기
3/7

들어가며

지난 글에서 Spring Container가 Bean을 관리하고, 싱글톤으로 유지하며, 생명주기까지 책임진다는 걸 알았다. 그렇다면 자연스럽게 이런 질문이 생긴다.

  • 브라우저에서 URL을 입력하고 엔터를 누르면, Spring 내부에서 정확히 무슨 일이 벌어질까?
  • Controller가 수십 개일 때, Spring은 어떻게 올바른 Controller를 찾아갈까?
  • 요청 데이터를 받는 방법이 여러 가지인데, 각각 언제 써야 할까?

이번 글에서 이 세 가지 질문에 답한다.


1. 전체 흐름 — 프런트 데스크부터 번역가까지

HTTP 요청이 서버에 들어왔을 때, Spring MVC 내부에서 벌어지는 일을 순서대로 보면 이렇다.

브라우저 요청
    ↓
DispatcherServlet       → 모든 요청의 단일 입구 (프런트 데스크)
    ↓
HandlerMapping          → 어떤 Controller가 처리할지 찾기 (지도)
    ↓
HandlerAdapter          → Controller 메서드를 어떻게 호출할지 변환 (통역사)
    ↓
Controller              → 실제 비즈니스 로직 처리 후 Java 객체 반환
    ↓
HttpMessageConverter    → Java 객체 → JSON 변환 (번역가)
    ↓
브라우저 응답

각 단계의 역할을 하나씩 살펴본다.

DispatcherServlet — 프런트 데스크

모든 HTTP 요청이 가장 먼저 도착하는 곳이다. 대형 호텔 프런트 데스크처럼, 손님(요청)을 직접 맞이하고 적절한 곳으로 안내한다. 다른 Servlet으로 넘기는 게 아니라, 자기가 직접 교통정리를 한다.

HandlerMapping — 지도

Controller가 수십 개일 때, 이 요청을 누가 처리해야 하는지 찾아주는 지도다. @GetMapping("/users") 같은 어노테이션이 바로 이 지도를 작성하는 행위다.

HandlerAdapter — 통역사

Controller마다 메서드 형태가 다르다. 파라미터가 없는 것도 있고, @PathVariable을 받는 것도 있고, @RequestBody를 받는 것도 있다. HandlerAdapter가 중간에서 이 차이를 흡수해서 올바르게 호출해준다.

HttpMessageConverter — 번역가

Controller가 반환한 Java 객체를 브라우저가 이해할 수 있는 JSON으로 변환한다. @RestController를 쓰면 이 변환이 자동으로 일어난다.


2. 요청 데이터 받기 — 세 가지 방법

Controller에서 요청 데이터를 받는 방법은 세 가지다. 어디에 데이터가 담겨 있느냐에 따라 달라진다.

@PathVariable  →  URL 경로에 포함된 값   /users/42
@RequestParam  →  URL 뒤 쿼리스트링      /users?name=kim
@RequestBody   →  HTTP Body에 담긴 JSON  { "name": "kim" }

실제 코드로 보면 이렇다.

// URL 경로값으로 단건 조회
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { ... }

// 쿼리스트링으로 조건 검색
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name) { ... }

// JSON Body로 생성
@PostMapping("/users")
public User createUser(@RequestBody UserDto dto) { ... }

언제 뭘 쓰는지 정리하면 이렇다.

상황어노테이션예시
URL 경로에 값이 포함될 때@PathVariable/users/42
검색 조건을 URL에 붙일 때@RequestParam/users?name=kim
JSON 데이터를 Body에 담을 때@RequestBodyPOST, PUT 요청

3. 응답 보내기 — ResponseEntity

단순 조회라면 객체를 그냥 반환해도 된다. 하지만 생성, 수정처럼 HTTP 상태코드를 명시적으로 지정해야 할 때는 ResponseEntity를 쓴다.

// Before: 상태코드 제어 불가
public User getUser(@PathVariable Long id) {
    return user; // 항상 200 OK
}

// After: 상태코드 직접 지정
public ResponseEntity<User> createUser(@RequestBody UserDto dto) {
    return ResponseEntity.status(201).body(user); // 201 Created
}

주요 상태코드는 이렇다.

상태코드의미언제
200 OK성공조회
201 Created생성 성공POST
400 Bad Request클라이언트 오류잘못된 요청
404 Not Found리소스 없음존재하지 않는 데이터
500 Internal Server Error서버 오류서버 내부 문제

ResponseEntity를 항상 써야 하는 건 아니다. 팀 컨벤션에 따라 모든 응답을 통일하는 경우도 있고, 필요할 때만 쓰는 경우도 있다.


4. @Controller vs @RestController

@Controller + @ResponseBody = @RestController

@Controller는 View(HTML 페이지)를 반환한다. @RestController@ResponseBody가 추가된 것으로, View를 찾지 않고 Java 객체를 바로 HttpMessageConverter에 넘겨 JSON으로 변환한다.

REST API 서버를 만든다면 @RestController를 쓰면 된다.


마치며

Spring MVC는 DispatcherServlet이 모든 요청을 받아 HandlerMapping으로 Controller를 찾고, HandlerAdapter로 호출하고, HttpMessageConverter로 JSON 변환해서 응답하는 흐름이다. 요청 데이터는 위치에 따라 @PathVariable, @RequestParam, @RequestBody로 받고, 상태코드 제어가 필요하면 ResponseEntity를 쓴다.

profile
Backend Developer | Aspiring Full-Stack Enthusiast

0개의 댓글