지난 글에서 요청 파라미터(query string, form-data)를 @RequestParam, @ModelAttribute로 바인딩하는 법을 보았다.
이번에는 HTTP 요청 메시지(body) 자체를 읽어오는 방법을 살펴본다. 실무에서는 특히 JSON 본문을 주고받는 REST API가 많기 때문에 @RequestBody 사용을 익혀두는 게 중요하다.
아래 예제는 text 본문과 JSON 본문을 각각 “초기 버전 → 점점 스프링 방식 → 최종 버전”의 흐름으로 설명한다.
1. text 본문 읽기: RequestBodyStringController
v1. 서블릿 API로 직접 읽기 (가장 기초)
@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
response.getWriter().write("ok");
}
- 흐름: request.getInputStream() 으로 바이트 스트림을 꺼내고 → StreamUtils.copyToString 으로 문자열로 변환 → response.getWriter() 로 직접 응답 작성.
- 특징: 동작 원리를 이해하는 데 유용하지만, 코드가 길고 매번 스트림을 다뤄야 한다
v2. 스프링이 스트림을 주입받아 사용
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
responseWriter.write("ok");
}
- 흐름: 서블릿 의존성을 줄이고, 스프링이 요청 입력 스트림과 응답 Writer를 직접 주입해준다.
- 특징: v1보다 결합도가 낮고 테스트가 쉽다.
v3. HttpEntity로 본문과 헤더를 포괄적으로 다루기
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("ok");
}
- 흐름: HttpEntity<String> 에서 본문(body) 를 바로 꺼낸다. 필요하면 헤더도 다룰 수 있다.
- 특징: 응답도 HttpEntity 로 반환해 상태/헤더/본문을 제어할 수 있다.
v4. 최종 버전: @RequestBody로 본문 바로 받기
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("messageBody={}", messageBody);
return "ok";
}
- 흐름: @RequestBody 가 HTTP 메시지 본문을 문자열로 바로 바인딩한다. 응답은 @ResponseBody 로 본문에 직접 쓴다.
- 특징: 가장 간결하고 실무에서 권장되는 형태다.
→ text 본문 처리의 최종 형태는 v4 방식으로 정리하면 된다.
2. JSON 본문 읽기: RequestBodyJsonController
v1. 서블릿 + ObjectMapper로 수동 파싱
@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String messageBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody); // {"username":"kim","age":20}
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", data.getUsername(), data.getAge());
response.getWriter().write("ok");
}
- 흐름: 문자열로 읽은 뒤, ObjectMapper 로 직접 JSON → 객체(HelloData) 변환.
- 특징: 동작 이해에는 좋지만, 컨트롤러마다 수동 파싱 코드를 반복해야 한다.
v2. @RequestBody String + ObjectMapper (반자동)
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
- 흐름: 본문을 문자열로 받고, 변환만 ObjectMapper 로 수행.
- 특징: v1보다 간단하지만 여전히 변환 코드를 써야 한다.
v3. 최종 버전: @RequestBody DTO (메시지 컨버터 자동 변환)
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
- 흐름: @RequestBody HelloData 로 선언하면, 스프링의 메시지 컨버터(HttpMessageConverter) 가 JSON → HelloData 를 자동으로 변환한다.
- 특징: 컨트롤러는 비즈니스 로직에만 집중할 수 있다.
→ JSON 본문 처리의 최종 형태는 v3 방식으로 정리하면 된다.
참고: JSON 요청을 보내는 클라이언트는 Content-Type: application/json 헤더를 반드시 포함해야 한다.
3. 그래서 어떻게 사용하나?
결국 실전에서 아래 두 가지 형태만 기억하면 된다.
text 본문은 @RequestBody String 으로 받고, JSON 본문은 @RequestBody DTO 로 받으면 된다.
text 본문 최종
@Slf4j
@RestController
public class TextApiController {
@PostMapping("/text")
public String handleText(@RequestBody String messageBody) {
log.info("messageBody={}", messageBody);
return "ok";
}
}
JSON 본문 최종
@Slf4j
@RestController
public class HelloApiController {
@PostMapping("/hello")
public HelloData handleJson(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return data; // JSON으로 자동 변환되어 응답
}
}
4. 언제 @RequestBody, 언제 @ModelAttribute?
- @ModelAttribute
- 데이터 출처: 요청 파라미터 (query string, form-data)
- 예시: /search?username=kim&age=20
- 적합한 경우: 검색 조건, 로그인·회원가입 등 간단한 폼 입력 처리
- @RequestBody
- 데이터 출처: HTTP 요청 본문 (JSON/XML/text)
- 예시: { "username": "kim", "age": 20 }
- 적합한 경우: REST API 요청, 클라이언트–서버 간 JSON 통신, 외부 시스템 연동
마무리하며
요약하면, 폼 전송이나 쿼리 파라미터는 @ModelAttribute, JSON 본문은 @RequestBody 를 사용하면 된다.
앞에서 본 버전 1, 2처럼 직접 스트림을 꺼내거나 ObjectMapper로 JSON을 일일이 변환하는 방식은 이미 스프링이 내부에서 구현해 두었다.
덕분에 개발자는 최종 버전 코드처럼 애노테이션 하나만 붙여주면 요청 데이터를 객체로 바인딩하고, 응답까지 JSON으로 자동 변환할 수 있다.
즉, 실전에선 다음 세 가지 페턴을 중점적으로 기억하면 된다.
- text 본문 → @RequestBody String
- JSON 본문 → @RequestBody DTO
- 폼 데이터(query string, form-data) → @ModelAttribute
스프링이 반복적이고 저수준의 작업을 모두 처리해주기 때문에, 우리는 비즈니스 로직에만 집중하기에 스프링은 정말 좋은 프레임워크이다.
감사합니다.
'스프링' 카테고리의 다른 글
| [스프링] Validation에 대하여 (0) | 2025.09.27 |
|---|---|
| [스프링] 메시지, 국제화 (0) | 2025.09.19 |
| [스프링] 스프링 MVC - 기본기능 (2) 요청 파라미터 (0) | 2025.09.03 |
| [스프링] 스프링 MVC - 기본기능 (1) 로깅 (0) | 2025.09.03 |
| [스프링] 컬렉션 조회 최적화 (4) | 2025.07.31 |
