일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- spring3 spring2 traceid
- java
- spring MVC
- java lambda
- @FunctionalInterface
- micrometer tracing
- aws secretmanager
- EnableWebMvc
- kotlin
- HashMap
- Sleuth
- elasticsearch
- SpringMVC
- spring
- awssecretsmanagerpropertysources
- map
- b3-propagation
- list
- Spring JPA
- asyncconfigurer
- traceId
- traceasynccustomautoconfiguration
- ResponseBody
- CompletableFuture
- Spring Boot
- jpa
- java list
- asynccustomautoconfiguration
- DeferredImportSelector
- java.util.list
- Today
- Total
du.study기록공간
[Spring MVC] @ResponseBody 응답에 대하여 알아보자(1) 본문
API를 만들때, Json형태의 응답값을 내려주기 위하여 @ResponseBody 라는 어노테이션을 사용합니다. 이 어노테이션을 사용하면 본문 자체를 응답값으로 내려주기에 유용하게 사용하고 있지만. 정확하게 어떻게 동작하는지를 확인하기 위해서 내부 동작을 확인해보려고 합니다.
그중에서도 현재 RequestMappingHandlerAdapter 를 사용하는 케이스만 구현하고 있어 먼저 이 핸들러 어뎁터 기준으로 디버깅을 하여 확인한 결과를 작성합니다.
결론부터 간략하게 정리해보면 다음과 같습니다.
1. DispatcherServlet에서 핸들러 어뎁터(RequestMappingHandlerAdapter) 실행
2. HandlerMethodReturnValueHandler에서 RequestResponseBodyMethodProcessor 획득
3. RequestResponseBodyMethodProcessor 에서 다음 일 수행
- 응답본문에 Spring으로 값 기록
- view관련 체크 함수 타지않도록 mavContainer.setRequestHandled(ture); 선언
4. 응답 본문에 결과 리턴
이제 코드를 조금씩 까보면서 확인을 해보겠습니다.
첫번째로는 일반적인 Spring을 return하는 핸들러에 대한 응답방식을 확인해 보려합니다. (예시는 아래코드)
@RequestMapping("/hello")
@ResponseBody
public String hello() {
return "Hello SpringMVC";
}
먼저 헨들러를 시작은 DispatcherServlet doDispatch 의 해당 부분부터 시작이 됩니다.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
해당부분을 통해서 핸들러 어뎁터가 실행되면서 RequestMappingHandlerAdapter에서 invocableMethod.invokeAndHandle()호출을 통해서 핸들러 실행 및 반환타입 설정들을 결정하게 됩니다.
invocableMethod 의 본체인 ServletInvocableHandlerMethod를 살펴보겠습니다. 보려고 했던 부분만 요약하여..
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
.....
if (returnValue == null) {
.....
}
else if (StringUtils.hasText(getResponseStatusReason())) {
.....
}
mavContainer.setRequestHandled(false);
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
.....
}
}
해당 부분을 호출하면서 returnValue는 return값인 Hello SpringMVC를 가지고 있으며, 해당 핸들러 메서드에 responseStatusReason 이 존재하지 않아서 if, else문은 타지않게됩니다.( 추후에 이걸 타는 케이스를 찾아볼 예정입니다.)
결국 mavContainer.setRequestHandled(false); 를 하고 handlerReturnValue를 실행하게됩니다. 이 코드를 확인해보면
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
크게 핸들러 선택, 리턴벨류값 세팅으로 이뤄지게 됩니다.
먼저 @ResponseBody를 설정하게되면 HandlerMethodReturnValueHandler 중에서도RequestResponseBodyMethodProcessor 를 매칭하여 가져오게 됩니다. 과정을 살펴보면, 찾는 과정에서 해당 클레스 안에 있는함수가 매칭이 되어 true를 반환하기 때문에 HandlerMethodReturnValueHandler 를 사용하게 됩니다.
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
결국 handler.handleReturnValue는 RequestResponseBodyMethodProcessor를 호출하게 되는데, 이 안에서 결국 다음과 같은 과정이 일어납니다.
mavContainer.setRequestHandled(true);
......
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
return new ServletServerHttpResponse(response);
}
......
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
}
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
위에서 false로 세팅했던 값이 true로, 메세지를 string 값에 담아서 ServletServerHttpResponse Body에 내용을 기록 하여 리턴하게 됩니다.
마지막으로 다시 RequestMappingHandlerAdapter 로 돌아와서는 해당 문구를 실행합니다.
return getModelAndView(mavContainer, modelFactory, webRequest);
하지만, 메서드 안에서 바로 해당 부분을 체크하게 되면서 null을 반환합니다.
if (mavContainer.isRequestHandled()) {
return null;
}
/**
* Whether the request has been handled fully within the handler.
*/
public boolean isRequestHandled() {
return this.requestHandled;
}
(위에서 true로 세팅이 변경되었기에 그대로 종료)
그 이후엔 다른 DispatcherServlet를 실행해 나간다음 본문의 내용을 보여주게 됩니다.
어펜딕스 용으로.. 만약 @ResponseBody를 선언하지 않고 view를 호출하는 경우,HandlerMethodReturnValueHandler 에서는 ViewNameMethodReturnValueHandler를 가져오며, view 파일 조회 및 렌더등을 진행하게 됩니다.
'스프링' 카테고리의 다른 글
[Spring MVC] @Validated(@Valid) and BindingResult (0) | 2019.11.12 |
---|---|
[Spring MVC] @ResponseBody 응답에 대하여 알아보자(2) (0) | 2019.11.10 |
[Spring MVC] Handler Interceptor (0) | 2019.11.10 |
[Spring MVC] @EnableWebMvc 활용하기 (0) | 2019.11.08 |
[Spring MVC] web.xml 사용하지 않고 Spring MVC를 사용해보자 (0) | 2019.10.17 |