사용자의 요청을 제일 먼저 받는 Front Controller, 스프링 MVC는 Front Controller 패턴 으로 구현돼 있다. DispatcherServlet도 HttpServlet을 상속 받고 Servlet으로 동작한다. 스프링 부트는 DispatcherServlet을 Servlet으로 자동으로 등록하면서 모든 경로(urlpattern = “/“) 에 대해 매핑한다.
사용자로 부터 들어오는 모든 요청을 제일 먼저 받아 적절한 Handler(Controller) 를 찾아 호출한다.
모든 요청은 DispatcherServlet을 거치기 때문에 모든 요청에 공통적인 작업 이 가능한다.
요청 흐름
서블릿이 호출 되면 HttpServlet에서 제공하는 service 메소드가 호출된다.
스프링 MVC는 DispatcherServlet의 부모인 FrameworkServlet 에서 service를 오버라이드 했다.
FrameworkServlet service를 시작으로 여러 메소드를 호출되면서 DispatcherServlet의 doDispatch 메소드가 호출된다.
DispatcherServlet 동작 과정 살펴보기
Handler 조회
HandlerMapping을 이용해 요청에 매핑된 핸들러를 조회한다.
핸들러를 조회할 때 요청 URL을 이용해 많이 일치하는 순서대로 Handler들을 반환받는다.
HandlerAdapter 조회
Handler를 실행할 수 있는 HandlerAdapter를 조회한다.
HandlerInterceptor의 preHandler 메소드 실행
HandlerAdapter를 실행
HandlerAdapter는 요청을 처리할 수 있는 Handler를 실행시킨다.
처리 결과로 ModelAndView를 반환받는다.
반환된 ModelAndView 객체에 View가 없을 경우 RequestToViewNameTranslator를 이용해 적절한 View를 가져온다.
// 1. 현재 요청을 처리하기 위한 Handler(HandlerExecutionChain 객체)를 가져온다. mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; }
// 2. 현재 요청에 해당하는 HandlerAdapter를 가져온다. HandlerAdapterha= getHandlerAdapter(mappedHandler.getHandler());
// 3. HandlerInterceptor가 있다면 preHandler를 실행시킨다. Stringmethod= request.getMethod(); booleanisGet= HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { longlastModified= ha.getLastModified(request, mappedHandler.getHandler()); if (newServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } }
// 3. 등록된 인터셉터의 preHandle 메서드를 적용합니다. if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
// 4. HandlerAdapter 객체를 이용해 Request를 Handler로 처리한 후 ModelAndView 객체를 반환 받는다. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) { return; }
// 5. ModelAndView객체에 View name이 없을 경우 RequestToViewNameTranslator를 이용해 View name을 설정한다. applyDefaultViewName(processedRequest, mv);
// 6. HandlerInterceptor가 있다면 postHandler를 실행시킨다. mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = newNestedServletException("Handler dispatch failed", err); }
// 7. DispatcherServlet 결과에 대한 후처리를 진행한다. // Handler 결과에서 예외가 떨어질 경우 예외처리를 진행하고 적절한 View가 있을 경우 Rendering을 진행한다. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, newNestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
1. 요청을 처리하기 위한 Handler 가져오기
getHandler 메소드는 DispatcherServlet이 가지고 있는 HandlerMapping 객체들 중에서 전달 받은 Request를 처리할 수 있는 Handler 객체를 가져온다.
HandlerMapping 객체는 Request(요청)와 Request를 처리하기 위한 Handler의 Mapping 정보를 관리하는 관리하는 객체다.
HandlerMapping 객체는 getHandler 메소드를 통해 Handler 와 HandlerInterceptor 를 갖고 있는 HandlerExecutionChain 객체를 가져온다.
// 1. ModelAndView 객체가 비었는지 확인, 2. View or Model 데이터가 있는지 확인한다. if (mv != null && !mv.wasCleared()) { // View Rendering을 호출한다. render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isTraceEnabled()) { logger.trace("No view rendering, null ModelAndView returned."); } }
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; }
if (mappedHandler != null) { // Exception (if any) is already handled.. mappedHandler.triggerAfterCompletion(request, response, null); } }
View를 렌더링 한다.
render 메소드에서는 ViewResolver를 이용해 View 객체를 가져온 후 View 객체를 렌더링 해준다.
protectedvoidrender(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)throws Exception { // Determine locale for request and apply it to the response. Localelocale= (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale);
View view; StringviewName= mv.getViewName();
if (viewName != null) { // ViewResolver를 이용해 View 객체를 가져온다. view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { thrownewServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { thrownewServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } }
// Delegate to the View object for rendering. if (logger.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // View 객체를 렌더링 해준다. view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; } }
resolveViewName 메소드는 viewName과 일치하는 View 객체를 가져온다.