@RequestBody
, @ResponseBody
에 대해 좀 더 자세히 배운다.@RequestBody
두 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
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
그럼 위 글을 보면 결국 RequestMappingHandlerAdapter
이 ArgumentResolver
을 호출해가지고 parameter들을 준비하는 것이라는 것을 알 수 있다. 그런데 이전 글에서 HTTP message를 JSON이나 XML로 변환하는 애들의 모체라는 HttpMessageConverter
을 소개했는데 이건 대체 언제 작동하는 것이냐고 할 수가 있다.
사실 ArgumentResolver
은 인터페이스로, RequestMappingHandlerAddapter
과 같은 adapter들을 위해 특정 annotation이나 method를 위한 parameter들을 준비하는 애들이 구현하는 것이다.
위의 javadoc을 보면 겁나 종류가 많다. 이 중 우리가 주목해야 하는 것은 RequestResponseBodyMethodProcessor
이다.
얘가 HttpMessageConverter
을 호출해서 parameter을 변환한다. JSON이니까 구체적으로 호출하는 HttpMessageConverter
은 MappingJackson2HttpMessageConverter
이 될 것이다. 이 때 사용 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이 된다. 관련 글
@ResponseBody
@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
다.
@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!");
}
이 때 produces
와 Accept
header의 비교는 이전에 잠깐 설명한 ContentNegotiationManager
이 담당한다. 사용하는 manager은 RequestMappingHandlerAdapter
에 등록된 manager을 사용한다. 구체적인 과정은
먼저 RequestResponseBodyMethodProcessor
이라는 녀석을 사용해야 한다는 것을 결정.
그 다음 ContentNegotiationManager
을 기반으로 구체적으로 어떤 HttpMessageConverter
을 사용할지 결정
이후 구체적으로 사용할 HttpMessageConverter
이 결정되고, 처리 후에 return value가 그 HttpMessageConverter
에 의해 변환되어가지고 response로 전달이 된다.
참고로, 위와 같이 코딩을 하게 되면 /content
관련 request의 Accept
header이 JSON, XML이 둘 다 없으면 406 status의 HTTP message를 자동으로 보내게 된다. 와!