DispatcherServlet을 분석하면서 추상적으로 어떤 역할들을 하는지 살펴봤었습니다. 이번에는 구체적인 HandlerAdapter들을 살펴보고 각각의 차이점을 살펴보겠습니다.
Handler는 우리가 @Controller
에 정의한 요청을 처리기라고 생각하면 되겠지만 Adapter는 무슨 역할을 해주는지 정확히 와닿지 않습니다.
그래서 이번에 코드를 직접 살펴보면서 이해해보겠습니다.
간단히 Spring MVC에서 어떤 Adapter들을 가지고 있는지 보겠습니다.
DispatcherServlet.properties
파일 속에 Default 전략으로 어떤 Adapter들을 추가하는지 확인할 수 있습니다.
org.springframework.web.servlet.HandlerAdapter=
org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
우선, @Controller
에서 정의한 핸들러들을 처리해줄 어댑터를 어떻게 찾을지 부터 알아보겠습니다.
DispatcherServlet
내의 doDispatch
메소드 중 HandlerAdapter를 찾는 부분입니다.
// HandlerAdapter
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
...
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
getHandlerAdapter
메소드는 단순히 DispatchServlet이 초기화해놨던 HandlerAdapter들을 순회하면서 mapping된 Handler를 처리할 수 있을 경우 반환하는 역할을 합니다.
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
Iterator var2 = this.handlerAdapters.iterator();
while(var2.hasNext()) {
HandlerAdapter adapter = (HandlerAdapter)var2.next();
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
실제 구현체의 supports
함수 내부에서 어떤 기준으로 adapter가 handler처리를 지원하는지 살펴보면 될 것 같습니다.
public final boolean supports(Object handler) {
return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod)handler);
}
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
단순히 HandlerMethod 인스턴스이면 해당 어댑터가 지원함을 알 수 있습니다.
결국 default 전략을 사용할 경우, 대부분의 Handler에 대한 처리는 초기화시기에 adapter등록 순서에 의해 정해지게 됩니다.
handdler 를 실행시켜준다.
라고만 정리를 했었지만 여기서는 보다 구체적으로 정의해보겠습니다.
RequestMappingHandlerAdapter
의 handlerInternal
코드입니다.
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
this.checkRequest(request);
ModelAndView mav;
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized(mutex) {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader("Cache-Control")) {
if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
this.prepareResponse(response);
}
}
return mav;
}
checkReqeust
arugment로 받은 HttpServletRequest의 HTTP Method값을 확인해서 처리가능한지 확인합니다. 처리할 수 없으면 Exception을 발생시킵니다.protected final void checkRequest(HttpServletRequest request) throws ServletException {
String method = request.getMethod();
if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
} else if (this.requireSession && request.getSession(false) == null) {
throw new HttpSessionRequiredException("Pre-existing session required but none found");
}
}
그리고 Session을 가지고 있을 경우, mutex를 활용해서 thread-safe하게 처리하는 것을 볼 수 있습니다. 이러한 처리를 봐서 session을 사용할 경우와 사용하지 않을 경우, 성능차이가 생길 것을 알 수 있습니다.
마지막으로 handler처리후 보내줄 응답에서 Cache-Control
Header와 관련된 처리를 해주는 코드가 있음을 확인 할 수 있습니다.
사실 가장 중요한 부분은 handler를 실행 시켜주는 this.invokeHandlerMethod
메소드입니다.
@Controller
를 사용할 때 핵심적으로 살펴봐야할 부분은 바로 아래 부분입니다. 아마 요청을 처리하는 handler를 정의하는 것이 Spring 으로 웹개발을할 때 가장 많이 알아야하고 사용하는 부분일 것 입니다.
각종 Annotation과 Argument 와 return 타입을 잘 알고 다루는 것이 중요한데 invokeHandlerMethod
가 그 부분을 담당해서 유연하게 처리해줍니다.
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
Object result;
try {
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
if (!asyncManager.isConcurrentHandlingStarted()) {
ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
return var15;
}
result = null;
} finally {
webRequest.requestCompleted();
}
return (ModelAndView)result;
}
아직 전체 코드를 완전히 파악할 수는 없지만
WebDataBinderFactory
를 이용해서 data binding을 처리.ModelFactory
를 이용한 모델생성.WebAsyncManager
를 통해 비동기 처리에 관여함을 추측할 수 있습니다.그리고 마지막으로 ModelAndView
형태로 handler를 처리함을 확인할 수 있습니다.