본문 바로가기
스프링부트

@RequestBody, @ResponseBody 의 동작 원리

by pius712 2023. 10. 21.

HandlerMethodArgumentResolver 와 HandlerMethodReturnValueHandler의 동작에 대해서 알아야하는데, 이를 알아보기전에 스프링 부트에서 http 요청이 어떻게 처리되는지 우선 알아야한다.

dispatcher servlet

http 요청이 들어오면 dipatcher servlet의 doDispatch 메서드를 거치게 된다.

doDispatch 메서드를 간소화하자면,

  1. getHandler 를 통해서, 요청을 처리할 수 있는 Handler 를 찾는다.
  2. handler 를 처리할 수 있는 handlerAdapter 를 찾는다.
  3. handlerAdapter를 통해서 실제 handler 를 호출한다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;

		// Determine handler for the current request.
		mappedHandler = getHandler(processedRequest);
		// Determine handler adapter for the current request.
		HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

		// Actually invoke the handler.
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
	}

RequestMappingHandlerAdapter

@RequestMapping 또는 @RestController 어노테이션이 이 붙은 컨트롤러의 경우,

기본적으로 RequestMappingHandlerAdapter 를 통해서 처리가 된다.

해당 어댑터의 코드에서 invokeHandlerMethod 메서드를 간소화해서 보면,

  1. invocableMethod 에 arugment resolver, retunValue handler를 등록한다.
  2. 그리고 invocableMethod 를 호출한다.
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		if (this.argumentResolvers != null) {
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		if (this.returnValueHandlers != null) {
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}		
		invocableMethod.invokeAndHandle(webRequest, mavContainer);

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}

HandlerMethodArgumentResolver

위에서 등록된, invocableMethod 가 호출될 때 메서드의 argument에 대해서 argument resolver 를 수행하게 된다.

  1. argument (parameter) 를 loop 를 돌면서 resolver 호출 결과를 반환한다.
  2. 이렇게 resolver 가 적용된 arguments를 반환하게 된다.
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();

		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
		}
		return args;
	}

returnValue handler의 경우에도 마찬가지로 동작한다.

@ResponseBody, @RequestBody 도 동일하게 동작한다.

@RequestBody 는 argument resolver를 통해 동작하고,

@ResponseBody 는 returnValue handler 를 통해 동작한다.

이 둘은 RequestResponseBodyMethodProcessor 라는 이름으로 동작한다.

method의 parameter type, return type 의 어노테이션을 보고 적용 여부를 판단하고,

parameter나 return value에 converter를 통해서 값을 변환하여 쓴다.

// RequestResponseBodyMethodProcessor

@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

@Override
public boolean supportsReturnType(MethodParameter returnType) {
	return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
			returnType.hasMethodAnnotation(ResponseBody.class));
}

내부적으로는 http message converter 들을 통해서, byte, string, jackson 처리를 모두 가능하게 한다.

요약

spring boot는 request에 대해서 request adapter 에 위임하여, handler 수행을 한다.

request adapter 는, handler 가 어떻게 처리할지에 대한 책임을 가진 객체로, argument와 return value를 resolve 하는 책임을 가지고 있다.

@RequestBody, @ResponseBody 도 이런 resolver 를 통해 처리된다.