출처 : https://stackoverflow.com/questions/2769467/what-is-dispatcher-servlet-in-spring
위 사진처럼 Dispatcher, Mapping, Controller Adapter, Model, Resolver, View Routing과 같은 다양한 역할을 한다
public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
다음과같이 setApplicationContext를 통해 ApplicationContext를 주입받아 스프링컨테이너에 직접적으로 접근할 수 있게 해주는 인터페이스이다
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
this.webApplicationContext = (WebApplicationContext) applicationContext;
this.webApplicationContextInjected = true;
}
}
FrameWorkServlet에선 위의 setApplicationContext를 구현해 WebApplicationContext를 등록한다 이를 조금더 추적해보면
다음과같이 AnnotationConfigServletWebServletApplicationContext를 볼 수 있는데 해당 스프링컨테이너는 WebMvc를 이용할때 가장 근본이 되는 Context이다 이 부분에 대해서는 추후에 자세하게 포스팅 할 예정이다
다시 돌아와서 DispatcherServlet에 대해 알아보자
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
우선 DispatcherServlet은 Bean이다 위 코드를 보면 AutoConfiguration에 의해 BeanFactory에 등록된다 (= 스프링 컨테이너의 관리대상)
다른 Bean들과의 차이점으로는 Bean이 다른 Bean들에 직접적으로 접근할 수 있는 Bean이다 아래내용을 더 살펴보자
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext wac = null;
...
onRefresh(wac);
...
}
FrameWorkServlet은 하위클래스(DispatcherServlet)에게 onRefresh에대한 처리를 위임한다
public interface Servlet {
...
public void init(ServletConfig config) throws ServletException;
...
}
위 메서드는 실제 서블릿컨테이너가 서블릿을 인스턴스화 한 후 실행한다
( FrameworkServlet또한 Servlet을 구현한 객체이다 )
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
위에 설명을 토대로 주입된 ApplicationContext를 갱신(Refresh)할 때 DispatcherServlet은 다양한 리졸버들을 전략패턴을 이용해 생성한다
private void initMultipartResolver(ApplicationContext context) {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
...
}
다음과같이 Context(내부에 BeanFactory)로부터 Bean을 가져와 등록한다
이외 Locale, ThemeResolver, viewNameTranslator, flashMapManager가 위의 방식처럼 하나의 Bean을 바로 할당한다
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context,HandlerMapping.class, true, false);
this.handlerMappings = new ArrayList<>(matchingBeans.values());
}
HandlerMappings는 BeanFactoryUtils을 통해 ParentContext까지 찾아서 HandlerMapping.class를 구현한 모든 Class를 Bean으로 만들어 Map에 저장한다
이외 HandlerAdapter, HandlerExceptionResolver, ViewResolver가 위와같은 방식으로 할당한다
위의 모든 Resolver들은 등록된 Bean이 없으면 DispatcherServler.properties에 등록된 ClassPath들을 Mapping한다
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
...
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
...
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
...
}
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
...
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter
...
위 메서드들은 DispatcherServlet의 초기화 과정에 대한 내용이고 이 아래부턴 실제 service & dispatch에 대한 내용이다
protected void service(HttpServletRequest req, HttpServletResponse resp) {
String method = req.getMethod();
...
if (method.equals(METHOD_GET)) {
...
doGet(req, resp);
}else if (method.equals(METHOD_POST)) {
doPost(req, resp);
}
}
실제 요청이 들어오면 위의 정의된 내용에따라 하위클래스인 FrameworkServlet의 processRequest가 실행된다
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
...
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
doService(request, response);
...
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
}
LocaleContext, RequestAttributes, Async와 관련된 내용을 처리하며 HttpMethod의 처리는 템플릿 메서드 패턴을 이용해 하위클래스인 DispatcherServlet.doService에게 구현을 위임시킨다
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
doDispatch(request, response);
...
}
doService에서는 실제 내용을 처리한 dispatch를 위해 각종 atrritbute를 설정해둔다
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
processedRequest = checkMultipart(request);(=return this.multipartResolver.resolveMultipart(request)) // MultipartResolver
HandlerExecutionChain mappedHandler = getHandler(processedRequest); // Handler
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Adapter
if (!mappedHandler.applyPreHandle(processedRequest, response)) { // PreInterceptor
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // Controller Handle
applyDefaultViewName(processedRequest, mv); // FindViewName
mappedHandler.applyPostHandle(processedRequest, response, mv); // PostInterceptor
...
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception); // HandlerException
errorView = (mv != null);
}
}
if (mv != null && !mv.wasCleared()) {
render(mv, request, response); // View
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
...
}
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);
...
if (viewName != null) {
view = resolveViewName(viewName, mv.getModelInternal(), locale, request); // ViewResolver
}else {
view = mv.getView();
}
view.render(mv.getModelInternal(), request, response); // render
}
정상처리의 경우
MultipartResolve -> getHandler(Mapping) -> getAdapter -> HandlerExecutionChain(PreInterceptor -> Handle(+returnValue) -> ViewName -> PostInterceptor) -> Render(LocalResolver, resolveViewName, view.render())
에러처리의 경우
MultipartResolve -> getHandler(Mapping) -> getAdapter -> HandlerExecutionChain(PreInterceptor -> Handle(+returnValue) -> ViewName -> PostInterceptor) -> HandlerException -> Render(LocalResolver, resolveViewName, view.render())
각종 에러, 정상처리 등의 완료작업에는 Interceptor.Complete가 수행된다
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception
doDispatch는 왜 deprecation이 되었을까 ?