Baeldung의 이 글을 정리 및 추가 정보를 넣은 글입니다.

1. Introduction

  • 이 글에서는 이전 글에도 나왔던 @RequestBody, @ResponseBody에 대해 좀 더 자세히 배운다.

2. @RequestBody

  • javadoc

  • 두 annotation은 marshalling과 관련이 있다.

  • 이 중 @RequestBody는 request에서 온 요청을 특정 Java object로 marshalling하는 데 활용된다.

  • 이전 글에서 언급했지만, 변환을 해주는 class는 HttpMessageConverter이라고 불린다. 이 converter이 저 annotation이 있는 object로 request에 온 메세지를 변환시킨다. 밑의 코드의 경우 이전 글이랑 비슷하게 Jackson library만 dependency에 추가하고 @EnableMvc 외의 다른 별다른 설정을 하지 않았다면, loginForm이라는 object를 request에서 온 JSON data를 기반으로 생성하게 된다.

@PostMapping("/request")
public ResponseEntity postController(
  @RequestBody LoginForm loginForm) {
 
    exampleService.fakeAuthenticate(loginForm);
    return ResponseEntity.ok(HttpStatus.OK);
}

ResponseEntity라는 것이 처음 등장하는데, 이게 뭔지는 다음에 알아보도록 하자. 일단 이걸 return 하면 위 parameter로 받은 code의 HTTP response가 생성되고 client에게 전달된다는 것만 알자.

  • 해당 object에 관한 내용물이 '꼭' 필요한지를 required field로 표기하는 것이 가능하고, 해당 object 내용물이 유효한지 atomic하게 확인하기 위해 @Valid라는 annotation을 조합하는 것도 가능하다.

  • 이전 글에도 언급했지만 구체적인 동작은 이 글을 참고하면 된다. 여기서 핵심적인 녀석은 바로 RequestMappingHandlerAddapter이다.

RequestMappingHandlerAdapter

  • javadoc

  • documentation을 보면 @RequestMapping이 annotate된 HandlerMethod들의 처리를 지원하는 class라고 되어 있다.

  • 앞에 소개한 글이 잘 나와 있는데 일단 한가지 지적해야 하는 것이, RequestMappingHandlerAdapter은 단순히 annotation 관련 내용 뿐만 아니라 @RequestMapping동작에 필요한 요소들을 처리하는 것을 전부 관여한다. annotation 말고도 이거랑 관련된 것들 중 대표적인 것은 바로 ModelAndView가 있다.

  • 여하튼, 이 documentation에서 얘기하는 HandlerMethod는 controller에서 @RequestMapping이 annotate된 method를 말한다. 이 녀석이 잘 자동으로 동작할 수 있도록 온갖 함수들이 documentation에 있는 것을 볼 수 있는데, 여기서 주목해야 하는 것은 getArgumentResolvers이다. 이 method를 통해 호출한 HandlerMethodArgumentResolver이 바로 @RequestMapping annotate된 method의 parameter 준비를 담당하기 때문이다.

ArgumentResolver

  • javadoc

  • 얘의 동작은 앞서 소개한 글에서 잘 설명하고 있다.

  • 그럼 위 글을 보면 결국 RequestMappingHandlerAdapterArgumentResolver을 호출해가지고 parameter들을 준비하는 것이라는 것을 알 수 있다. 그런데 이전 글에서 HTTP message를 JSON이나 XML로 변환하는 애들의 모체라는 HttpMessageConverter을 소개했는데 이건 대체 언제 작동하는 것이냐고 할 수가 있다.

  • 사실 ArgumentResolver은 인터페이스로, RequestMappingHandlerAddapter과 같은 adapter들을 위해 특정 annotation이나 method를 위한 parameter들을 준비하는 애들이 구현하는 것이다.

  • 위의 javadoc을 보면 겁나 종류가 많다. 이 중 우리가 주목해야 하는 것은 RequestResponseBodyMethodProcessor이다.

  • RequestResponsebodyMethodProcessor javadoc

  • 얘가 HttpMessageConverter을 호출해서 parameter을 변환한다. JSON이니까 구체적으로 호출하는 HttpMessageConverterMappingJackson2HttpMessageConverter이 될 것이다. 이 때 사용 method는 readWithMessageconverters가 된다.

유의사항

  • 여기까지가 동작 원리다. 그러면 기능적으로 문제가 없으려면 무엇을 신경써야 할까?

  • 일단 request에서 전달하는 JSON의 structure이, 변환하려는 POJO랑 동일한 구조를 가져야 한다. 안 그러면 실패한다. 예를 들어 위 예시 코드의 LoginForm이 다음과 같이 되어 있다고 해보자.

public class LoginForm {
    private String username;
    private String password;
    // ...
}
  • 그러면 다음과 같은 구조를 가져야 한다.
{"username": "johnny", "password": "password"}
  • JSON의 field랑 POJO의 field가 이름이 같아야 한다는점 유의.

  • 해결되지 않은 POJO의 field value는 null이 된다. 관련 글

3. @ResponseBody

  • javadoc

  • @RequestBody랑 크게 다르지 않는데, 바로 return한 POJO를 적절히 변환해가지고 HTTP message에 집어넣어야 한다는 것을 의미한다.

  • 예시로 밑의 class가 있다고 해보자.

public class ResponseTransfer {
    private String text; 
    
    // standard getters/setters
}
  • 그리고 밑의 코드가 있다고 해보자.
@Controller
@RequestMapping("/post")
public class ExamplePostController {

    @Autowired
    ExampleService exampleService;

    @PostMapping("/response")
    @ResponseBody
    public ResponseTransfer postResponseController(
      @RequestBody LoginForm loginForm) {
        return new ResponseTransfer("Thanks For Posting!!!");
     }
}
  • 그러면 /response로 POST 요청을 하면 response의 payload에는 다음이 나타나게 된다.
{"text":"Thanks For Posting!!!"}
  • 유의할 점은, 위는 @Controller을 써서 method에 @ResponseBody를 붙여야 했지만 @RestController 사용시 모든 method에 자동으로 @ResponseBody가 붙은 거랑 같다는 점... 기억하자.

HandlerMethodReturnValueHandler

  • 변환 과정은 역시나 이 글 참고.

  • 이번에도 처음에는 RequestMappingHandlerAdapter이 관여를 하고 거기서 getReturnValueHandler을 호출하게 된다. 그러면 위의 경우 HttpMessageConverter은 언제 동작하게 될까?

  • 사실 앞서 소개한 RequestRepsonseBodyMethodProcessor이 저 인터페이스를 implement하는 AbstractMessageConverterMethodProcessor를 extend한다... 즉 이번에도 RequestResponsebodyMethodProcessor을 통해 HttpMessageConverter이 동작되며 이때 호출하는 method는 handleReturnValue다.

3.1 Setting the Content Type

  • @RequestMapping에는 produces라는 field가 있다. 생성할 response data type에 따라 사용할 controller method를 분류하는데 사용되는데, 이걸로 당연하지만, request에서 온 Accept에 따라 무슨 data type을 반환할지를 바꾸는데 사용할 수가 있다.

  • 과정은? 사실 produces만 잘 설정하면은 자동으로 이게 성립한다. 밑의 코드들의 위는 JSON을, 아래는 XML을 반환한다. 물론 대응되는 converter들이 미리 존재해야 한다.

@PostMapping(value = "/content", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseTransfer postResponseJsonContent(
  @RequestBody LoginForm loginForm) {
    return new ResponseTransfer("JSON Content!");
}
@PostMapping(value = "/content", produces = MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public ResponseTransfer postResponseXmlContent(
  @RequestBody LoginForm loginForm) {
    return new ResponseTransfer("XML Content!");
}
  • 이 때 producesAccept header의 비교는 이전에 잠깐 설명한 ContentNegotiationManager이 담당한다. 사용하는 manager은 RequestMappingHandlerAdapter에 등록된 manager을 사용한다. 구체적인 과정은

    • 먼저 RequestResponseBodyMethodProcessor이라는 녀석을 사용해야 한다는 것을 결정.

    • 그 다음 ContentNegotiationManager을 기반으로 구체적으로 어떤 HttpMessageConverter을 사용할지 결정

    • 이후 구체적으로 사용할 HttpMessageConverter이 결정되고, 처리 후에 return value가 그 HttpMessageConverter에 의해 변환되어가지고 response로 전달이 된다.

  • 참고로, 위와 같이 코딩을 하게 되면 /content 관련 request의 Accept header이 JSON, XML이 둘 다 없으면 406 status의 HTTP message를 자동으로 보내게 된다. 와!

profile
안 흔하고 싶은 개발자. 관심 분야 : 임베디드/컴퓨터 시스템 및 아키텍처/웹/AI

0개의 댓글