[Spring/TIL] @RequestParam, @ModelAttribute, @RequestBody 차이

2026. 1. 11. 15:16·Spring/TIL
반응형

 

사실 이 내용을 어렴풋이 알고는 있었지만, 남들에게 술술 설명할 수 있는 정도로 잘 아냐고 물어봤을 땐 그닥... 이라고 할 게 분명해서 글로 한 번 정리해보고자 했다.

 

Spring MVC에서 요청을 받을 때 사용하는 어노테이션은 여러 개가 있지만, 이 차이를 이해하는 데 필요한 개념은 되게 단순하다.

 

서버는 요청을 받으면 먼저 ‘종이 한 장’을 만든다

 

이 종이 한 장으로 @RequestParam, @ModelAttribute, @RequestBody를 한 번 설명해보겠다.

 

 


미리보기

 

흐름 설명

클라이언트가 요청을 보낸다.

 

쿼리 파라미터 방식

GET /login?email=a@test.com&password=1234

 

 

Form Data 방식

POST /login
Content-Type: application/x-www-form-urlencoded

email=a@test.com&password=1234

 

요청 방식이 뭐든, 서버는 먼저 다음과 같이 행동한다.

 

“요청에 값들이 있네. 일단 적어두자.”

 

그래서 서버는 내부적으로 종이 한 장을 만든다.

 

email = "a@test.com"
password = "1234"

이 종이는 데이터가 URL에서 왔는지, Body에서 왔는지 관심 없다.


값만 적혀 있으면 된다.

 

이게 흔히 말하는 파라미터 맵이다.

 


파라미터 맵(Parameter Map)이란?

파라미터 맵은 요청으로 들어온 값들을 서버가 한 번에 정리해 둔 일종의 표다.

URL 쿼리스트링이든, 폼 데이터든 서버는 먼저 해당 값들을 key-value 형태로 모아둔다.

username = kim
age = 20

 

이런 식으로 해당 요청에는 이런 값들이 있다라고 한 장의 메모처럼 정리해 둔 것이 바로 파라미터 맵이다.

 

왜 굳이 파라미터 맵이 필요할까?

컨트롤러단에서는 URL 형식이 뭔지, 폼으로 왔는지 관심이 없다.

컨트롤러에 도착하기 전에 서버가 미리 해당 요청에서 쓸 수 있는 값 목록을 정리해서 결과만 넘겨주기 때문이다.

 

그 덕분에 @RequestParam, @ModelAttribute 같은 어노테이션은 이 파라미터 맵에서 필요한 값만 꺼내 쓰는 역할만 한다.

 

자, 다시 그러면 들어온 요청이 뭐든 종이 한 장을 만들어냈고, 다음 단계는 해당 종이를 어떻게 할 지를 결정하는 것.

 


@RequestParam — 종이에서 한 줄만 떼어낸다

@RequestParam String email

종이에서 email 부분만 찢어서 주는 느낌.

  • 객체를 생성하지 않음
  • 값 하나만 필요할 때 사용

 

@ModelAttribute — 종이 전체로 객체를 만든다

@ModelAttribute LoginReq req

종이에 적힌 내용으로 객체를 만들어서 주는 느낌

  • 이 종이 한 장(파라미터 맵)에는 요청 방식과 상관없이 이름과 값만 담겨 있다.
email = "a@test.com"
password = "1234"
 

Spring은 먼저 LoginReq의 빈 객체를 하나 만든다.

LoginReq req = new LoginReq();

 

그 다음, 파라미터 맵을 한 항목씩 보면서 이름이 같은 필드를 객체에서 찾는다.

 

email이라는 이름을 보면, setEmail(...)을 호출하고, password라는 이름을 보면, setPassword(...)를 호출한다.

 

이렇게 해서 종이에 적혀 있던 값들이 객체의 필드로 옮겨진다.

 

여기서 중요한 점

@ModelAttribute는 Form Data 자체를 읽는 게 아니다

 

읽는 건 오직 종이다.

GET /login?email=...&password=...
@GetMapping("/login")
public void login(@ModelAttribute LoginReq req)

 

일반적으로 Form Data를 읽는다고 하면 당연히 POST만 가능하겠구나 생각할 것인데, GET 요청도 가능하다.

 

GET인지 POST인지는 상관없이 이미 파라미터 맵으로 만들어진 것을 읽기 때문이다.

 

어쨌든 핵심은 GET이든 POST든 상관없다는 것.


종이가 만들어지기만 하면 된다.

 


그런데 JSON은?

POST /login
Content-Type: application/json

{
  "email": "a@test.com",
  "password": "1234"
}

 

파라미터 맵은 key=value&key=value 쌍으로 이루어져 있을 때만 생성이 가능한데, JSON 규약과는 다르기 때문에 종이가 만들어지지 않는다. 

@ModelAttribute LoginReq req

따라서 위와 같은 방식은 당연히 불가능.

 

결국, 앞선 종이 한 장을 만들지 않고 요청 본문을 그대로 읽어서 객체로 만드는 것이다.

 


@ModelAttribute 바인딩은 어떻게 이루어질까

앞에서 말했듯이, @ModelAttribute는 파라미터 맵(종이)을 보고 객체를 만든다.
조금 더 정확히 말하면, 필드 단위로 값을 바인딩한다.

 

이 과정은 대략 다음 순서로 이루어진다.

 

Spring은 먼저 대상 타입(LoginReq)의 빈 객체를 하나 생성한다.

LoginReq req = new LoginReq();

 

단, 여기에서 사용되는 객체는 기본 생성자가 존재해야만 한다.

 

그 다음, 파라미터 맵을 하나씩 순회하면서 각 항목을 확인한다.

email = "a@test.com"
password = "1234"

Spring은 각 key 이름을 기준으로 객체 내부를 탐색한다.

  • email이라는 이름의 필드가 있는지
  • 또는 setEmail(...) 형태의 setter가 있는지

찾아낸 뒤, setter를 호출하는 방식으로 값을 주입한다.

req.setEmail("a@test.com");
req.setPassword("1234");

 

즉, @ModelAttribute의 바인딩은 객체 전체를 한 번에 매핑하는 것이 아니라 필드 단위로, setter 기반으로 이루어진다.

 

이 때문에 일반적으로 @ModelAttribute를 사용하는 DTO는 자바 빈 규칙(기본 생성자 + setter)을 따르는 형태가 된다.

 


타입 변환은 어디서 일어날까

파라미터 맵에 들어 있는 값은 모두 문자열이다.

age = "20"

하지만 DTO의 필드는 문자열이 아닐 수 있다.

private int age;

이때 "20"을 20으로 변환하는 작업은 WebDataBinder가 담당한다.

 

즉, @ModelAttribute 바인딩 과정에는

  • 문자열 → 숫자
  • 문자열 → enum
  • 문자열 → 날짜

와 같은 타입 변환 로직이 함께 포함되어 있다.

 

그래서 별도의 변환 코드를 작성하지 않아도 자연스럽게 DTO가 채워지는 것이다.

 


@ModelAttribute vs @RequestBody 결정적인 차이

여기서 중요한 차이가 하나 생긴다.

 

@ModelAttribute는

  • 파라미터 맵을 기반으로
  • 필드 이름을 기준으로
  • setter를 호출해 값을 채운다

반면 @RequestBody는 전혀 다른 방식으로 동작한다.

 

@RequestBody는 JSON 문자열 전체를 대상으로 역직렬화(deserialization) 를 수행한다.

 

이 과정에서는

  • 기본 생성자 + setter 방식이 아니라
  • 필드 접근 또는 생성자 기반 매핑
  • Reflection을 이용한 객체 구성

이 사용된다. 즉,

  • @ModelAttribute
    파라미터 맵 기반, 필드 단위 바인딩
  • @RequestBody
    JSON 원문 기반, 객체 전체 역직렬화

그럼 이 일은 누가 다 하는 걸까?

여기서 조금만 더 깊게 들어가 보자. 우리가 편하게 어노테이션만 붙이면 되는 이유는 Spring 내부에서 누군가 열심히 교통정리를 하고 있기 때문이다.

 

HandlerMethodArgumentResolver

컨트롤러 메서드의 파라미터를 보고 "누가 이 일을 처리할지" 결정하는 관리자다.

  • @RequestParam이나 @ModelAttribute가 붙어 있다?
    • 종이(파라미터 맵)를 보고 처리
  • @RequestBody가 붙어 있다?
    • 이건 종이 말고, Body 내용 바로 읽어서 처리

 

@ModelAttribute가 선택되었을 때 - WebDataBinder

  • ArgumentResolver가 "객체로 만들어야 돼"라고 하면 WebDataBinder가 등장한다.
  • 이 친구가 **파라미터 맵(종이)**을 보고 객체의 Setter를 호출해서 값을 채워 넣는다.
  • 타입 변환(문자열 "10" → 숫자 10)이나 검증(Validation)도 얘가 담당한다.

 

@RequestBody가 선택되었을 때 - HttpMessageConverter (Jackson)

  • 종이가 없으므로, Body에 있는 JSON 데이터를 직접 읽어야 한다.
  • 이때 우리가 흔히 쓰는 Jackson 같은 라이브러리가 동작해서 JSON 문자열을 자바 객체로 변환(Parsing)해준다.
  • 반대로 @ResponseBody를 써서 리턴할 때도 이 친구가 자바 객체를 JSON으로 바꿔서 내보낸다.

최종 정리

  1. @RequestParam
    • 종이(파라미터 맵)에서 한 줄만 쏙 뺀다.
  2. @ModelAttribute
    • WebDataBinder가 종이 전체 내용을 보고 Setter로 객체를 채운다.
  3. @RequestBody
    • 종이가 없다. **HttpMessageConverter(Jackson)**가 JSON을 통째로 읽어서 객체로 바꾼다.

결국 파라미터 맵(종이)을 거치느냐, 아니냐와 그걸 누가 처리해주느냐(Binder vs Converter)의 차이만 알고 있으면 될 것 같다.

 

반응형
저작자표시 (새창열림)

'Spring > TIL' 카테고리의 다른 글

[Spring/TIL] 전역 예외 처리(@RestControllerAdvice) vs try-catch, 개념 확실히 잡기  (0) 2026.01.18
'Spring/TIL' 카테고리의 다른 글
  • [Spring/TIL] 전역 예외 처리(@RestControllerAdvice) vs try-catch, 개념 확실히 잡기
Dongni
Dongni
배우고 느낀 것들, 작은 코드 한 줄부터 일상의 순간까지, 성장의 흔적들을 기록하고자 합니다.
    반응형
  • Dongni
    BitBard
    Dongni
  • 전체
    오늘
    어제
  • 공지사항

    • #include <HelloWorld!.h>
    • 분류 전체보기 (26)
      • 회고 (0)
      • Algorithm (6)
      • BOJ (2)
      • Glitch Guide (2)
      • Database (2)
        • SQL (1)
        • TIL (1)
      • Balloon Map (0)
      • HTTP (1)
        • TIL (1)
      • Java (4)
        • TIL (3)
        • Guide (1)
      • Spring (2)
        • TIL (2)
      • 우아한 테크코스 (6)
        • 일기장 (1)
        • 회고록 (3)
        • 품앗이 (2)
      • Docker (1)
        • TIL (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Github
    • Youtube
    • BOJ
  • 인기 글

  • 태그

    코드 56
    오물풍선
    Maps API
    파일컨벤션
    windows가 이 디바이스의 클래스 구성을 설치 중입니다. (코드 56)
    version 23h2
    네이버 지도 api
    스프링부트
    윈도우 11 와이파이 연결
    노트북 와이파이 아이콘 사라짐
    springboot
    코딩테스트
    java
    윈도우 11 와이파이 사라짐
    c++
    스프링
    윈도우 11 와이파이 아이콘
    sql
    spring
    Flyway컨벤션
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
Dongni
[Spring/TIL] @RequestParam, @ModelAttribute, @RequestBody 차이
상단으로

티스토리툴바