Controller에서 쓰는 Annotation 정리 -2 : @RequestBody, @ResponseBody

@RequestBody, @ResponseBody

JSON 데이터를 주고받을 때 사용하는 annotation

1. @RequestBody

이 어노테이션이 붙은 파라미터에는 HTTP 요청의 본문 body 부분이 그대로 전달된다. RequestMappingHandlerAdapter에는 HttpMessageConverter 타입의 메시지 변환기(message converter)가 여러 개 등록되어 있다. @RequestBody가 붙은 파라미터가 있으면 HTTP 요청의 미디어 타입과 파라미터의 타입을 먼저 확인한다. (dispatcher-servlet.xml 에서 확인) 메시지 변환기 중에서 해당 미디어 타입과 파라미터 타입을 처리할 수 있다면, HTTP 요청의 본문 부분을 통째로 변환해서 지정된 메소드 파라미터로 전달해준다.

쉽게 말하자면 @RequestBody 어노테이션을 이용하면 HTTP 요청 Body를 자바 객체로 전달받을 수 있다.
▶그렇기 때문에 Body가 존재하지 않는 Get 방식의 메소드에 @RequestBody 를 활용하는 것은 적합하지 않으므로, 에러가 발생하게 된다.

public ResponseEntity<ResultMessage> delete(@RequestBody Map<String, String> param) { }
  • 에러내용


2020-09-15 11:01:21.621 DEBUG 14976 --- [nio-9080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@7968225b 2020-09-15 11:01:21.621 WARN 14976 --- [nio-9080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException:
Required request body is missing:
public org.springframework.web.servlet.ModelAndView com.xxx.web.business.admin.controller.AdminContoller.masterDataList(org.springframework.data.domain.Pageable,java.util.Map<java.lang.String, java.lang.String>) throws java.lang.Exception] 2020-09-15 11:01:21.622 TRACE 14976 --- [nio-9080-exec-1] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.orm.jpa.EntityManagerHolder@16e70ebc] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@c012d22] from thread [http-nio-9080-exec-1] 2020-09-15 11:01:21.622 DEBUG 14976 --- [nio-9080-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor

 

즉, @RequestBody는 반드시 Post 요청과 함께 사용되어야 한다.

@RequestBody는 Json이나 XML과 같은 형태의 데이터를 Jackson 등의 MessageConverter를 활용하여 Java Object로 변환한다. 이러한 성질은 Parameter로 받은 데이터들을 자바 객체로 1대1로 매핑시켜주는 @ModelAttribute와 차이가 있다. 즉, @RequestBody는 POST방식으로 Json의 형태로 넘겨온 데이터를 객체로 바인딩하기 위해 사용할 수 있다.

 


✅ResquestBody는 Setter가 필요없다! (ObjectMapper)

1. POST 방식일 때

(in AbstractJackson2HttpMessageConverter.class)

→ JSON으로 데이터가 넘어올 경우 Jackson2HttpMessageConverter의 ObjectMapper를 사용하여 Setter가 없어도 값이 할당된다.

참고 : [https://jojoldu.tistory.com/407]

ObjectMapper란?

"ObjectMapper는 기본 POJO(Plain Old Java Objects) 또는 범용 JSON Tree Model(JsonNode)에서 JSON을 읽고 쓰는 기능과 변환 수행을 위한 기능을 제공합니다. 또한 다양한 스타일의 JSON 컨텐츠와 함께 작동하고 다형성 및 객체 동일성과 같은 고급 객체 개념을 지원하도록 Customizing할 수 있습니다."

Jackson의 ObjectMapper는 Java Object ←→ JSON 파싱을 쉽게 해주는 클래스 입니다.

Java 오브젝트를 Json으로 파싱하는 것을 직렬화(serialize)
반대는 역직렬화(deserialize)
라고 합니다.

[https://velog.io/@conatuseus/RequestBody%EC%97%90-%EC%99%9C-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%A0%95%EC%9E%90%EB%8A%94-%ED%95%84%EC%9A%94%ED%95%98%EA%B3%A0-Setter%EB%8A%94-%ED%95%84%EC%9A%94-%EC%97%86%EC%9D%84%EA%B9%8C-2-ejk5siejhh]

JSON → Java로 파싱해야되니깐 역직렬화를 찾기

in ObjectMapper.class

 

2. GET 방식일 때

  • Get 요청의 경우 JSON 데이터가 아닌 Query Parameter이다. (?aaa=ddd&bbb=111)
  • 그래서 Jackson2HttpMessageConverter 를 사용하지 않음. 이럴 경우 Spring에서 WebDataBinder를 사용합니다.
  • 기본값으로 값을 할당하는 방법이 Java Bean 방식
  • Spring 에서는 WebDataBinder의 기본 값 할당 방법인 Java Bean 방식을 사용하니 Setter가 없으면 작동하지 않음.

 


결론

✔ 화면에서 JSON 데이터로 보낼 경우 -> requestBody

@PostMapping("/join")
public ResponseEntity<ResultMessage> join(@RequestBody JoinVO join, HttpServletRequest req) throws Exception {
        HttpSession session = req.getSession();
        ...
 	return ResponseEntity.ok(ResultMessage.builder()
                                              ...
                                              .build()
                                              );
 }

 

 

✔ 화면에서 form으로 submit 할 경우 -> VO 객체로 파라미터 받을 수 있음.
이 소스는 화면에서 form submit 하므로 데이터 타입이 JSON이 아니니까 @requestBody 빠짐

@RequestMapping("/join01")
public ModelAndView join03(HttpServletRequest req, JoinVO join) throws Exception {
  ModelAndView model = new ModelAndView();

  return model;
}

 


 

2. @ResponseBody

@ResponseBody@RequestBody와 비슷한 방식으로 동작한다. @ResponseBody가 메서드 레벨에서 부여되면 메서드가 리턴하는 오브젝트는 뷰를 통해 결과를 만들어내는 모델로 사용하는 대신, 메시지 컨버터를 통해 바로 HTTP 응답의 메시지 본문으로 변환된다.

간단히 이야기 하자면, 요청한 형태에 맞춰서 메시지 변환기를 통해 결과값을 반환한다. @ResponseBody는 @RequestBody가 선택한 형식으로 결과값을 변환하여 반환한다고 보면 된다. 또한 @ResponseBody을 이용하면 자바 객체를 HTTP 응답 body로 전송할 수 있다.

✅ResponseEntity<T> 와 @ResponseBody 차이
  • ResponseEntity<T> and @ResponseBody 는 똑같은 결과를 가지고 온다.
  • ResponseEntity를 사용하는 이유는 HTTP response header의 융통성있는 추가가 가능하기 때문.

 

@ResponseBody

@RequestMapping(value = "/message")
@ResponseBody
public Message get() {
    return new Message(penguinCounter.incrementAndGet() + " penguin!");
}

 

ResponseEntity<T>

@RequestMapping(value = "/message")
ResponseEntity<Message> get() {
    Message message = new Message(penguinCounter.incrementAndGet() + " penguin!");
    return new ResponseEntity<Message>(message, HttpStatus.OK);
}
ModelAndView는 view를 반환하기 위해 사용. 하지만 JSON/XML 데이터를 반환할 경우에는 @ResponseBody 어노테이션을 붙여줌으로서 JSON 형태로 데이터 반환.

내가 이해한 바 ~ ResponseEntity의 경우 json으로 결과값을 받아야하는 ajax로 호출할 때 쓰는 것이 좋고, ModelAndView 같은 경우는 화면이 전환되거나 타임리프로 submit 데이터 날릴 때 쓰기.

 

 


 

 

결론

  1. JSON 데이터 주고받을 때, @RequestBody, @ResposeBody 사용
  2. @RequestMapping@PostMapping, @GetMapping으로 쓸 수 있음
  3. form submit 하면 controller에서 파라미터로 Java 객체 바로 받을 수 있음
  4. return 을 ResponseEntity 로 받으면 ResposeBody 쓸 필요 없음.