DispatcherServlet은 SpringMVC의 메인이라고 할 수 있을만큼 여러 기능을 수행한다. 그림에서 볼 수 있듯이 여러 컴포넌트들의 사이에서 중개자 역할을 하며 요청을 HandlerAdapter에 넘겨 처리하고, 처리한 결과를 응답에 맞게끔 후처리해주는 역할을 한다.
코드를 뜯어보면서 DispatcherServlet의 각 메서드가 어떤 역할을 하는지 살펴봤는데, 이번에는 정리하는 겸 각 역할을 하는 메서드를 살펴보며 마무리해보려 한다.
HandlerMapping 조회
// DispatcherServlet
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
dispatcherServlet은 handlerMappings라는 handlerMapping 목록을 유지하고 있다. request를 매개변수로 받게 되면, handlerMappings를 순회하며 요청을 처리할 수 있는 handler를 찾게 된다. 이 때, handler는 HandlerExecutionChain 타입으로 반환되며, 요청을 처리하는 handler뿐만 아니라 handler에 대한 여러 interceptor까지 함께 포함하고 있다.
주요 HandlerMapping은 다음과 같다
1. RequestMappingHandlerMapping
2. SimpleUrlHandlerMapping
3. BeanNameUrlHandlerMapping
요새는 @Controller, @RestController 기반의 handler를 사용하기 때문에 사실상 이를 처리하는 RequestMappingHandlerMapping이 거의 다 사용된다고 볼 수 있다.
HandlerAdapter 목록 조회
// DispatcherServlet
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");
}
getHandler를 통해 찾은 handler는 getHandlerAdapter 메서드를 통해 handler를 처리할 수 있는 HandlerAdapter를 찾게 된다.
여기서도 adapter가 해당하는 handler를 처리할 수 있는지를 확인하고 해당하는 adapter를 반환하게 된다.
주요 HandlerAdapter는 다음과 같다.
1. RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
2. HttpRequestHandlerAdapter : HttpRequestHandler 처리
3. SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션x, 과거에 사용) 처리
마찬가지로, RequestMappingHandlerAdapter가 대부분 사용된다고 보면 된다.
HandlerAdapter 호출
이렇게 찾은 handlerAdapter는 doDispatch 메서드 내에서 handle 메서드를 실행하게 된다.
// DispatcherServlet.doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
RequestMappingHandlerAdapter는 AbstractHandlerMethodAdapter로부터 handle 메서드를 호출하고, 다시 handleInternal을 호출하게 된다.
// AbstractHandlerMethodAdapter.handle
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return this.handleInternal(request, response, (HandlerMethod)handler);
}
handleInternal
handleInternal은 요청을 처리하기 전에 세션 동기화 여부, 캐시 설정 등을 확인하는 로직을 포함한다.
이런 전처리 과정이 끝나면 this.invokeHandlerMethod를 통해 실질적으로 handler 호출 로직이 실행된다.
// RequestMappingHandlerAdapter
@Nullable
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;
}
invokeHandlerMethod
비동기 관련 코드가 많고, 중간에 ModelFactory와 관련된 코드들이 많은데, RequestMappingHandlerAdapter는 추후에 따로 정리할 계획이기 때문에, 핵심만 보자면 가장 아래 줄에 getModelAndView 호출 부분이다.
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
...
// 비동기 처리가 시작된 경우 null 반환 (동기 처리가 끝났으므로 ModelAndView 반환하지 않음)
return asyncManager.isConcurrentHandlingStarted() ? null : this.getModelAndView(mavContainer, modelFactory, webRequest);
}
getModelAndView 메서드에서 드디어 mavContainer로부터 model을 받아오고, 이를 이용해 ModelAndView 인스턴스를 만들어 반환하는 것을 확인할 수 있다.
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
} else {
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View)mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
RedirectAttributes redirectAttributes = (RedirectAttributes)model;
Map<String, ?> flashAttributes = redirectAttributes.getFlashAttributes();
HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
}
postHandle
다시 dispatcherServlet으로 돌아오면, 이렇게 받은 ModelAndView에 대해 interceptor를 통해 postHandle을 적용시켜 후처리를 해준다.
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
processDispatchResult
doDispatch 메서드는 handler로부터 받은 결과를 응답하기 위해 processDispatchResult 메서드를 호출하는데 여기서 에러 화면의 처리와 render 메서드를 호출하게 된다.
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
ModelAndViewDefiningException mavDefiningException = (ModelAndViewDefiningException)exception;
this.logger.debug("ModelAndViewDefiningException encountered", exception);
mv = mavDefiningException.getModelAndView();
} else {
Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
mv = this.processHandlerException(request, response, handler, exception);
errorView = mv != null;
}
}
if (mv != null && !mv.wasCleared()) {
this.render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else if (this.logger.isTraceEnabled()) {
this.logger.trace("No view rendering, null ModelAndView returned.");
}
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}
}
}
render
호출된 render 메서드는 ModelAndView로부터 view를 받아, 이를 render하게 되고 템플릿 엔진에 맞게 렌더링되게 된다.
JSP를 사실 많이 사용을 안해봐서 JSP의 내부 동작은 제대로 모르지만 view의 구현체 중 Thymleaf가 있는 것을 보면 전체적인 동작은 비슷하나 JSP는 InternalResourceView를 사용한다고 하니, 아마 내부적으로 조금 다른 동작을 갖는 것 같다.
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
response.setLocale(locale);
String viewName = mv.getViewName();
View view;
if (viewName != null) {
view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
String var10002 = mv.getViewName();
throw new ServletException("Could not resolve view with name '" + var10002 + "' in servlet with name '" + this.getServletName() + "'");
}
} else {
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
} catch (Exception var8) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Error rendering view [" + view + "]", var8);
}
throw var8;
}
}
RestController를 사용할 때는 HttpMessageConverter를 사용하는데, 이에 대해서는 따로 다시 얘기를 해보려 한다.
'Web > Spring' 카테고리의 다른 글
Spring은 비동기 처리를 한 후에 어떻게 응답할까? - 1 : 요청 쓰레드의 반환 (0) | 2024.10.25 |
---|---|
[Spring Web 6.1] DispatcherServlet 뜯어보기 - Multipart & ExceptionHandling (1) | 2024.10.07 |
[Spring Web 6.1] DispatcherServlet 뜯어보기 - view 관련 (0) | 2024.10.07 |
[Spring Web 6.1] DispatcherServlet 뜯어보기 - dispatch (0) | 2024.10.07 |
[Spring Web 6.1] DispatcherServlet 뜯어보기 - getter & setter (0) | 2024.10.04 |