du.study기록공간

[Spring MVC] @ResponseBody 응답에 대하여 알아보자(1) 본문

스프링

[Spring MVC] @ResponseBody 응답에 대하여 알아보자(1)

du.study 2019. 11. 10. 22:19
728x90

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 파일 조회 및 렌더등을 진행하게 됩니다.

 

 

 

 

728x90
Comments