지난 글에서 Spring Container가 Bean을 관리하고, 싱글톤으로 유지하며, 생명주기까지 책임진다는 걸 알았다. 그렇다면 자연스럽게 이런 질문이 생긴다.
이번 글에서 이 세 가지 질문에 답한다.
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를 쓰면 이 변환이 자동으로 일어난다.
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에 담을 때 | @RequestBody | POST, PUT 요청 |
단순 조회라면 객체를 그냥 반환해도 된다. 하지만 생성, 수정처럼 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를 항상 써야 하는 건 아니다. 팀 컨벤션에 따라 모든 응답을 통일하는 경우도 있고, 필요할 때만 쓰는 경우도 있다.
@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를 쓴다.