스프링의 @RequestMapping Handler Method 중 요청값을 받아올 때 자주 사용되는 것으로 다음 3가지가 있습니다.
- @ModelAttribute
- @RequestParam
- @RequestBody
특히 @ModelAttribute 와 @RequestParam 은 사용하는 방법이 비슷하여 "둘 다 똑같은거 아니야?" 라고 쉽게 착각 할 수 있습니다.
(저 혼자만의 착각 일 수 있습니다.)
그럼 @ModelAttribute 와 @RequestParam의 차이를 알아보면서 추가로 @RequestBody는 또 어떤 기능이 있는지 알아보겠습니다.
@ModelAttribute
메서드 인자에 @ModelAttribute 애노테이션을 사용하여 model에 있는 attribute에 접근할 수 있습니다. (attribute가 없다면 model의 인스턴스만 생성합니다.)
Http Servlet 요청 매개변수의 이름과 메서드 인자의 이름이 일치한다면 데이터 바인딩을 해줍니다.
이 때 데이터를 바인딩 해주면서 유효성 검사도 함께 해줄 수 있습니다.
즉, 쿼리 파라미터를 개별적으로 가져와서 변환하는 작업을 하지 않아도 데이터 바인딩을 해줍니다.
파라미터 이름을 명시하지 않으면 필드명과 일치하는 필드의 Setter를 이용해서 데이터 바인딩을 합니다.
다음과 같은 방법으로 Pet 인스턴스를 Resolved 합니다.
- Model Attribute 가 있다면 model을 통해서
- @SessionAttribute 에 값이 있다면 Http Session 을 통해서
- 기본 생성자를 통해서
- 쿼리 파라미터(Query Parameter)나 form 필드와 일치하는 인자를 받는 "첫 번째 생성자"를 통해서.
인자의 이름은 자바빈 @ConstructorProperties로 지정하지 않으면 런타임에 바이트코드에 있는 파라미터 명을 사용합니다.
데이터 바인딩은 model attribute 인스턴스를 생성한 다음 진행합니다. WebExchangeDataBinder 가 쿼리 파라미터와 form필드 이름을 바인딩할 Object 필드명과 비교한다. 필요하다면 필드 매칭 전 타입을 반환합니다.
데이터 바인딩을 커스텀 하고 싶다면 DataBinder를 참고하세요.
데이터 바인딩에 실패하면 기본적으로 WebExchangeBindException 예외가 발생합니다.
컨트롤러 메서드에서 예외를 처리하고 싶으면 @ModelAttribute 다음 인자로 BindResult를 받으면 됩니다.
@ModelAttribute는 생략해도 됩니다.
예를 들어 애노테이션을 생략하고, 파라미터 대신 attribute를 매핑할 수도 있습니다.
적당한 리졸버가 없고 BeanUtils#isSimpleProperty 결과가 false면 @ModelAttribute를 선언한 것과 동일하게 처리합니다.
@RequestParam
쿼리 파라미터를 컨트롤러의 메서드 인자로 바인딩 합니다.
1:1로 매칭하여 하나의 값만을 전달해줍니다.
만약 어떤 클래스가 생성자가 1개의 인자를 받는데, 해당 인자가 @RequestParam 으로 바인딩 해오는 값과 일치한다면 해당 클래스의 인스턴스를 주입해줍니다. 해당 예제를 아래를 확인해주세요!
Servlet API 에서 "Request Parameter"는 Query Parameter, form Data, multiparts 모두를 포함하는 개념입니다.
하지만 웹플럭스에서는 ServerWebExchange를 사용해 각각 따로 접근하며, @RequestParam은 쿼리 파라미터만 바인딩합니다.
Query Parameter, form Data, multiparts를 모두 사용하려면 @ModelAttribute 로 바인딩하면 됩니다.
@RequestBody
@RequestBody 애노테이션을 붙이면 HttpMessageReader가 request body를 Java Object로 역직렬화 합니다.
@RequestBody도 javax.validation.Valid나 스프링의 @Validated 어노테이션을 붙이면 표준 빈 검증 방식으로 유효성을 확인할 수 있습니다.
Post 요청에 @RequestBody로 데이터를 받는다면 Setter가 필요없다?
spring에서 Json의 역직렬화(Json을 자바 객체로 변환)를 담당하는 것은 Jackson2HttpMessageConverter입니다.
즉, @RequestBody 로 Json데이터가 넘어오면 Json을 JavaObject로 변환해주는 것은 Jackson2HttpMessageConverter입니다.
이런 역직렬화는 ObjectMapper의 readValue 메서드를 사용해서 변환하기 때문에 setter가 필요없는것입니다.
(단, 기본 생성자는 대부분의 경우에 필요합니다.)
Get 요청에도 Setter가 필요없다??
Get 요청의 경우 Json데이터가 아닌 Query Parameter로 받습니다.
그래서 Jackson2HttpMessageConverter가 아닌 Spring의 WebDataBinder를 사용합니다.
WebDataBinder는 기본 설정으로 자바빈 방식으로 값을 바인딩합니다.
자바빈 방식이란 setter를 활용해서 값을 할당하는 것을 의미합니다.
따라서 Get 요청에서는 Setter가 없으면 값을 받아올 수 없습니다.
자세한 내용
[RequestBody에서는 Setter가 필요없다?] - 이동욱님
[@RequestBody에 왜 기본 생성자는 필요하고, Setter는 필요없을까? #1]
차이점
@RequestParam
- 1:1로 값을 바인딩 해줍니다.
- 만약 1:1로 받는값이 메서드 매개변수의 생성자(1개의 인자만 받는)의 인자와 일치하면 객체를 바로 바인딩 해줍니다.
- 잘못된 파라미터값이 들어오면 400 BadRequest를 발생시킵니다.
@ModelAttribute
- 여러 파라미터를 매개변수에 바인딩해줄 수 있습니다.
- setter를 사용해 담아주기 때문에 해당 매개변수의 바인딩 받는 필드는 setter가 있어야 합니다.
- 타입 변환에 실패하더라도 작업은 계속됩니다. (WebExchangeBindException 발생
@RequestBody
- Post 요청시 기본생성자가 있어야하며 Setter는 없어도 된다
- Get 요청시 Setter가 존재해야 한다.(기본 WebDataBinder 설정, 변경가능)
참고문헌
- docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-requestparam
-
'Spring' 카테고리의 다른 글
[Spring] Json 응답 gzip 압축하기 - 1 (2) | 2021.09.10 |
---|---|
[Spring] @TransactionalEventListener (0) | 2021.08.25 |
SpringBoot build.gradle 설정 (0) | 2021.01.26 |
스프링 트랜잭션에서 RuntimeException이 발생하면 롤백이 발생하는 이유 (2) | 2021.01.17 |
세션 저장소를 사용하는 3가지 방법 (0) | 2020.07.25 |
댓글