스프링부트 해부학 : RequestFlow(1) - DispatcherServlet

정윤성·2022년 6월 3일
2

스프링부트 해부학

목록 보기
1/20

역할


출처 : https://stackoverflow.com/questions/2769467/what-is-dispatcher-servlet-in-spring

위 사진처럼 Dispatcher, Mapping, Controller Adapter, Model, Resolver, View Routing과 같은 다양한 역할을 한다


Class Diagram

ApplicationContextAware.java

public interface ApplicationContextAware extends Aware {
	void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

다음과같이 setApplicationContext를 통해 ApplicationContext를 주입받아 스프링컨테이너에 직접적으로 접근할 수 있게 해주는 인터페이스이다

FrameworkServlet.java

@Override
public void setApplicationContext(ApplicationContext applicationContext) {
	if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
    	this.webApplicationContext = (WebApplicationContext) applicationContext;
    	this.webApplicationContextInjected = true;
	}
}

FrameWorkServlet에선 위의 setApplicationContext를 구현해 WebApplicationContext를 등록한다 이를 조금더 추적해보면

WebApplicationContext.java

다음과같이 AnnotationConfigServletWebServletApplicationContext를 볼 수 있는데 해당 스프링컨테이너는 WebMvc를 이용할때 가장 근본이 되는 Context이다 이 부분에 대해서는 추후에 자세하게 포스팅 할 예정이다

여담으로 WebFlux는 AnnotationConfigReactiveWebServerApplicationContext를 이용한다

다시 돌아와서 DispatcherServlet에 대해 알아보자


Dispatcher

구성

DispatcherServletAutoConfiguration.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이다 아래내용을 더 살펴보자

FrameworkServlet.initWebApplicationContext

protected WebApplicationContext initWebApplicationContext() {
	WebApplicationContext wac = null;
	...
    onRefresh(wac);
    ...
}

FrameWorkServlet은 하위클래스(DispatcherServlet)에게 onRefresh에대한 처리를 위임한다

public interface Servlet {
	...
	public void init(ServletConfig config) throws ServletException;
    ...
}

위 메서드는 실제 서블릿컨테이너가 서블릿을 인스턴스화 한 후 실행한다
( FrameworkServlet또한 Servlet을 구현한 객체이다 )

DispatcherServlet.onRefresh

@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은 다양한 리졸버들을 전략패턴을 이용해 생성한다

초기화 전략

MultipartResolver

private void initMultipartResolver(ApplicationContext context) {
	this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    ...
}

다음과같이 Context(내부에 BeanFactory)로부터 Bean을 가져와 등록한다

이외 Locale, ThemeResolver, viewNameTranslator, flashMapManager가 위의 방식처럼 하나의 Bean을 바로 할당한다

HandlerMappings

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가 위와같은 방식으로 할당한다

detectAllHandlerMappings값을 false로두면 MultipartResolver처럼 한개의 Bean만 할당받는다

기본값

위의 모든 Resolver들은 등록된 Bean이 없으면 DispatcherServler.properties에 등록된 ClassPath들을 Mapping한다

DispatcherServlet.getDefaultStrategies

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);
    ...
}

DispatcherServlet.properties

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
...

Service & Dispatch

Service

HttpServlet.doService

위 메서드들은 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가 실행된다

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에게 구현을 위임시킨다

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를 설정해둔다

Dispatch

DispatcherServlet.doDispatch

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가 수행된다


정리

  1. DispatcherServlet은 ApplicationContextAware를 구현한 클래스로써 ApplicationContext에대해 접근할 수 있다
  2. ApplicationContext의 등록된 FactoryBean을 통해 다양한 Resolver Bean을 불러와 저장한다
  3. DispatcherServlet또한 SingletoneBean으로 ApplicationContext가 Initailizer될 때 생성된다
  4. Dispatcher에 대한 정확한 방법을 알 수 있었다

Problem

@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception

doDispatch는 왜 deprecation이 되었을까 ?

profile
게으른 개발자

0개의 댓글