[SpringBoot 필독!] @RequestBody @ModelAttribute 비교

BlackBean99·2022년 7월 13일
5

SpringBoot

목록 보기
9/20
post-thumbnail
post-custom-banner

이걸 모르고 개발하다 고생한 내 모습..

SpringBoot API 개발시 많이들 모르고 넘어가는..

@RequestBody, @ModelAttribute 의 기능 차이입니다!
저도 PostMan 으로 테스트 해봤을때, JSON형식으로 request를 보냈습니다. 하지만 자꾸 반환값으로 Null이 나오는 것은 왜 일까요?

null Exception이 발생한다면 먼저 Controller Layer를 먼저 의심해보십쇼!

제가 이걸 몰라서 왜 그랬는지 한참을 해맸습니다.


아무튼

아래 코드를 봅시다

@PostMapping
public ResponseEntity<String> createPost(@ModelAttribute PostDto postDto)

@PostMapping
public ResponseEntity<String> createComment(@RequestBody CommentDto commentDto);

두 코드의 차이는 어노테이션 차이입니다.


Body를 어떤 형식으로 주냐에 따라서 받지 못하는 경우가 있습니다. 이는 SpringBoot내부의 MessageHandler가 어노테이션마다 다르기 때문인데요. 하나씩 짚어봅시다

1. @RequestBody

이는 HTTP(JSON, XML)을 Java 오브젝트로 변환합니다.
이 데이터는 Spring 내부에서 HttpMessageConverter를 통해 타입에 맞게 변환되는데,Dto에 JSON을 담아서 던지는 예제 코드와 함께 이야기 해봅시다.

| Controller

Controller 에서 @RequestBody를 사용해서 Dto를 받아보겠습니다

@PostMapping("/requestbody")
public ResponseEntity<RequestBodyDto> testRequestBody(@RequestBody RequestBodyDto requestBodyDto) {
    return ResponseEntity.ok(requestBodyDto);
}

| RequestBodyDto

public class RequestBodyDto {

    private String name;
    private long age;
    private String password;
    private String email;

    public RequestBodyDto() {
    }
}

Dto객체를 JSON 문자열 변환한 뒤, 이를 Post 요청을 하게 되서 Controller로 전달 됩니다!

Dto에 기본 생성자만 있고 @AllArgsConstructor 를 쓰지도 않았습니다.
어떻게 생성자 없이 Java 객체를 생성할 수 있었을까? 바로 계속 언급한 MessageConverter 입니다.

1-1)MappingJackson2HttpMessageConverter

@RequestBody org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver 클래스의 readWithMessageConverters()라는 메서드를 보면

Spring에 등록된 MessageConverter중

MappingJackson2HttpMessageConverter를 사용합니다.
이 Converter를 보면 내부에서 ObjectMapper를 통해서 JSON값을 Java 객체로 역직렬화를 하는데

  • ps) 역직렬화란?
    생성자를 쓰지 않고 리플렉션을 통해 객체를 만드는 것이다.

직렬화 가능한 클래스들은 기본 생성자를 선언 꼭꼭! 해줘야 한다. -> @RequestBody에 사용하는 Dto는 기본생성자를 정의하지 않으면 바인딩 실패!

Jackson ObjectMapper는 JSON 오브젝트의 필드를 Java 오브젝트에 맵핑할 때 getter,setter을 사용하는데 앞의 get, set를 지우고 나머지 문자의 첫 문자를 소문자로 변환한 문자열을 참조합니다.
그래서 RequestBodyDto에 getter, setter메서드를 모두 정의해줘야 합니다. 안하면 HttpMessageNotWritableException 발생합니다.

1. RequestBody 요약

  • @RequestBody를 사용하면 요청 본문의 JSON, XML, Text 등의 데이터가 적합한 HttpMessageConverter를 통해 파싱되어 Java 객체로 변환 된다.

  • @RequestBody를 사용할 객체는 필드를 바인딩할 생성자나 setter 메서드가 필요없다.

  • 직렬화를 위해 기본 생성자는 필수다.

  • 또한 데이터 바인딩을 위한 필드명을 알아내기 위해 getter나 setter 중 1가지는 정의되어 있어야 한다.

2. @ModelAttribute

api/url?name=req&age=1 같은 Query String 형태 혹은 요청 본문에 삽입되는 Form 형태의 데이터를 처리합니다!.

  • Body에 JSON형식으로 POST 보내도 못받는다는 이야기입니다!
  • contentType을 x-www-form-url-encoded로, 요청 본문 내용을 Form 형식으로 보내야 합니다
    테스트 예시
    rm(post("/modelattribute")
           .contentType(MediaType.APPLICATION_FORM_URLENCODED)
           .content("name=req&password=pass&email=naver"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("name").value("req"))
           .andExpect(jsonPath("pass").value("pass"))
           //...
    이런방식으로 보내야 응답을 성공합니다
    Controller
    @GetMapping("/modelattribute")
    public ResponseEntity<ModelAttributeDto> testModelAttribute(@ModelAttribute ModelAttributeDto modelAttributeDto) {
       return ResponseEntity.ok(modelAttributeDto);
    }

ModelAttributeDto

@Setter
public class ModelAttributeDto {

    private String name;
    private long age;
    private String password;
    private String email;

    //Getter만 정의
}

생성자 없어도 가능하다! 하지만 Setter는 있어야 ModelAttribute 에서 Binding이 가능하다!

2. ModelAttribute 요약

  • 객체에 데이터를 바인딩할 수 있는 생성자혹은 Setter메서드가 하나는 필요하다.
  • Query String (w-xxx-url-encoded)방식이나 Form 형식 데이터가 아니면 nul반환한다 ㅠㅠ
    - @RequestBody와 달리 @Valid 어노테이션을 통해 검증 절차를 추가할 수 있다.
profile
like_learning
post-custom-banner

0개의 댓글