[Spring] Spring MVC - @RequestParam,@ModelAttribute, @RequestBody 차이에 대해

최동근·2023년 2월 14일
0

스프링

목록 보기
4/8

Spring 에서는 클라이언트 측에서 보낸 데이터를 Java 코드에서 사용할 수 있는 객체로 만들어주는 것과 관련해서 @RequestParam , @ModelAttribute 그리고 @RequestBody 3가지 어노테이션을 제공합니다.
Spring 을 이용한 개발을 할때 각 어노테이션의 세부 수행 동작 원리에 대해 완벽하게 숙지하지 않은 채로 진행한다면, 얘기치 못한 에러의 늪에 빠지기 쉽습니다.
이번 포스팅에서는 3가지 어노테이션의 세부 기능과 차이점에 대해 알아보겠습니다 👨‍💻

🤖 @RequestParam

@RequestParam은 1개의 Http 요청 파라미터(변수) 를 받기 위해서 사용됩니다.

예제

@Slf4j
@ResponseBody // 응답 값을 Json 형태로 변환하여 Http Body 에 담는다.
@RequiredArgsConstructor
@RestController
public class TestController {

    @GetMapping("/request-param-test")
    public ResponseDto requestParamTest(
            @RequestParam(name = "name") String name,
            @RequestParam(name = "age") Integer age) {

        return new ResponseDto(name,age);
    }
}
@Getter
@Setter
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class ResponseDto {
    private String name;
    
    private Integer age;
    
    private String girlFriendYn;
}

@RequestParam 를 이용하여 클라이언트로 부터 넘어온 두 개의 Http 요청 파라미터를 받고 있습니다.
nameage Http 요청 파라미터를 받고 있는데, 만약 Http 요청 파라미터와 컨트롤러의 매개변수 이름이 동일하다면 (name = "xxx") 을 생략할 수 있습니다.

해당 예제를 Postman 으로 간단하게 테스트 해보겠습니다 💪


해당 이미지는 Postman 을 통해 요청을 보내는 이미지입니다.
Http 요청 파라미터로 알맞은 값을 설정하여 보내는 것을 확인할 수 있습니다.


@ResponseBody 를 이용했기 때문에 반환된 ResponseDto 객체가 Json 형태로 직렬화 되어 반환된 것을 확인할 수 있습니다.

@RequestParam 에는 required 속성과 defaultValue 속성이 있습니다.

  • @RequestParam.required
    • Http 요청 파라미터 필수 여부
    • Default 값은 True (요청 파라미터 필수)
    • False 일때 Http 요청 파라미터가 넘어가지 않는 경우 null 로 채워짐

  • @RequestParam.defaultValue
    • 요청 파라미터의 Default 값 설정 가능
    • 빈 문자가 와도 Default 값 적용
    • 해당 속성을 사용하는 경우 required 속성이 의미가 없음

예제

@Slf4j
@ResponseBody
@RequiredArgsConstructor
@RestController
public class TestController {

    @GetMapping("/request-param-test")
    public ResponseDto requestParamTest(
            @RequestParam(required = true) String name,
            @RequestParam(required = false) Integer age,
            @RequestParam(defaultValue = "N") String girlFriendYn) {

        return new ResponseDto(name, age, girlFriendYn);
    }
}

동일하게 Postman 을 통해 예제 테스트를 해보겠습니다.


1. required 속성 값이 true인 name 요청 파라미터를 생략하고 요청을 보내기👨‍💻

해당 요청에 대한 결과를 보겠습니다.
필수값인 name 이 설정되지 않았으니 에러가 뜨는것을 예상할 수 있겠죠?

예상처럼 400 에러 가 뜨는 것을 확인할 수 있습니다 😰

어플리케이션 콘솔창에도 동일하게
Required request parameter 'name' for method parameter type String is not present 에러 정보가 뜨는 것을 확인할 수 있습니다.


2. required 속성 값이 false인 age 요청 파라미터를 생략하고 요청을 보내기👨‍💻

이번에는 required 속성이 false 인 즉, 필수 요청 파라미터가 아닌 age 를 생략하고 요청을 보내보겠습니다.


예상대로 null 로 채워지는 것을 확인할 수 있습니다 ✌️


  1. defalutValue 속성 테스트해보기 👨‍💻

이번에는 defaultValue 속성값을 가지는 girlFriendYn 요청 파라미터를 생략하고 요청을 보내보겠습니다.

우리가 설정한 것처럼 "N" 으로 채워지는 것을 확인할 수 있습니다.
girlFriendYn 을 빈 문자열로 보내도 같은 결과를 확인할 수 있습니다.

🤖 @ModelAttribute

@ModelAttribute 는 multipart/form-data 형태의 HTTP body (form)내용과 HTTP 요청 파라미터를 Java 객체에 매핑(바인딩)합니다.

예제

@GetMapping("/model-attribute-test")
    public HelloData modelAttributeTest(
            @ModelAttribute HelloData helloData
    ) {
        return helloData;
}
@Getter
@Setter
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class HelloData {
    private String name;

    private Integer age;

    private Double height;

    private Double weight;
}

1. Http 요청 파라미터를 요청으로 보내기 👨‍💻

앞과 동일하게 Postman 을 통해 테스트를 해보겠습니다 👩‍🔬

HelloData 객체에 매핑(바인딩) 하기 위해 name,age,height,weight 요청 파라미터를 보냈습니다.

앞선 테스트와 동일하게 @ResponseBody 어노테이션을 사용하는 예제이기에 Json 형식으로 데이터가 정상적으로 넘어가는것을 확인할 수 있습니다.


2. Http 요청 Body 에 Form 데이터로 요청 보내기 👨‍💻

Json 형식의 요청을 보냈습니다. 결과는 어떨까요?

예상과 다르게 모든 필드가 null 로 반환된것을 확인할 수 있습니다 😰

@ModelAttribute 는 Query String 형태 혹은 Http 요청 Body 에 삽입되는 Form 형태의 데이터만 처리할 수 있습니다.

Form 형태의 데이터로 다시 요청을 보내보겠습니다 👀

Http 요청 Body 를 form-data 로 변경하니 우리가 예상한 결과가 나온것을 확인할 수 있습니다 💪

지금까지 @ModelAttribute 에 대해 알아보았습니다.
하지만 여기서 우리가 넘어간 정보가 있습니다.
그것은 매핑(바인딩) 할 객체는 Setter 가 있어야한다는 사실입니다❗️
앞에서 예제로 든 HelloData 클래스에서 Setter 를 제거해보고 다시 테스트 해보겠습니다.

@Getter
//@Setter -> Setter 생략
@Builder
@ToString
//@AllArgsConstructor -> 모든 필드에 대한 생성자 생략
@NoArgsConstructor
public class HelloData {

    private String name;

    private Integer age;

    private Double height;

    private Double weight;
}

HelloData 클래스를 이와 같이 변경 후 다시 테스트해보겠습니다.

Setter 를 생략하니 매핑(바인딩) 되지 않는 것을 확인할 수 있습니다 🤔

🤖 @RequestBody

@RequestBody 는 클라이언트가 보내는 Http 요청 Body(Json 및 XML) 을 Java 객체로 변환하는 역할을 합니다.

여기서 @RequestBody@ModelAttribute 경우의 매핑(바인딩) 방식과 달리 변환한다는 점에서 차이가 있습니다.

예제

@GetMapping("/request-body-test")
    public ResponseDto requestBody(
            @RequestBody ResponseDto request
    ) {
        return request;
 	}

앞과 동일하게 ResponseDto 객체를 사용해서 테스트해보겠습니다 👨‍💻

Http 요청 Body를 Json 형식으로 변경 후 테스트 진행했습니다.

요청에 대해서 정상적인 처리가 이루어진것을 확인할 수 있습니다 💪
지금까지 @RequestBody 에 대해 알아보았습니다.
하지만 @RequestBody@ModelAttribute 와 달리 Setter생성자 가 없이 기본 생성자 만 있어도 정상 작동하는데
두 방식에 차이에 대해 더 알아보겠습니다.

MappingJackson2HttpMessageConverter 에 대해

앞에서 강조했던 것처럼 @RequestBody 는 클라이언트의 Json 요청 데이터를 매핑(바인딩) 하는 것이 아니라 변환 과정을 통해 Java 객체를 생성합니다.
이때 작동하는 것이 Spring 의 여러 MessageConverter 중 하나인 MappingJackson2HttpMessageConverter 입니다.
해당 클래스를 통해 내부적으로 ObjectMapper 로 인해서 요청으로 들어온 Json 값을 Java 객체로 역직렬화합니다.
여기서 역직렬화 란 생성자를 거치지 않고 Reflection 을 통해 객체를 구성하는 매커니즘입니다.

Reflection 이란 클래스의 구체적인 정보를 모른 채 동적으로 객체를 생성할 수 있도록 도와주는 API 를 의미합니다.

즉, Reflection 이라는 마법같은 기능을 통해 해당 객체를 생성할 수 있습니다.
Reflection API 간단히 알아보자 을 참고해주세요 ❗️

🤖 정리

@RequestBody

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

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

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

  • getter/setter 중 1가지는 정의되어야 합니다.

@ModelAttribute

  • @ModelAttribute를 사용하면 HTTP 파라미터 데이터를 Java 객체에 맵핑합니다.
  • Setter 메소드가 필수적으로 필요합니다.
  • 해당 어노테이션은 생략 가능합니다.(그러나 명시적으로 써주는 것이 좋음)
  • Query String 혹은 Form 형식이 아닌 데이터는 처리할 수 없습니다.

참고

1. @RequestBody와 @ModelAttribute
[TIL] #6. 스프링 MVC - 기본 기능 ②
[Spring] @RequestParam, @RequestBody, @ModelAttribute 차이

profile
비즈니스가치를추구하는개발자

0개의 댓글