Spring MVC - Dispatcher Servlet(2)

2021. 8. 26. 14:52공부 정리/spring

1. 내부 동작 흐름


출처: https://cphinf.pstatic.net/mooc/20180219_281/1519003870301bOehw_PNG/2.png

Dispatcher Servlet의 처리과정을 크게 보면 이렇게 된다.

1. 요청 선처리 작업

2. 요청 전달

3. 요청 처리

4. 예외 처리

5. 뷰 렌더링

6. 요청 처리 종료

 

1. 요청 선처리 작업


출처: https://cphinf.pstatic.net/mooc/20180219_91/1519003885824QT31y_PNG/3.png

① Locale 결정

AcceptHeaderLocaleResolver가 default로 설정되어 있어서 따로 Locale 설정을 해주지 않으면 HTTP 헤더의 정보를 보고 지역정보를 설정해준다.

DispatcherServlet
/**
 * Build a LocaleContext for the given request, exposing the request's primary locale as current locale.
 * <p>The default implementation uses the dispatcher's LocaleResolver to obtain the current locale,
 * which might change during a request.
 * @param request current HTTP request
 * @return the corresponding LocaleContext
 */
@Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
	if (this.localeResolver instanceof LocaleContextResolver) {
		return ((LocaleContextResolver) this.localeResolver).resolveLocaleContext(request);
	}
	else {
		return new LocaleContext() {
			@Override
			public Locale getLocale() {
				return localeResolver.resolveLocale(request);
			}
		};
	}
}

 

② RequestContextHolder에 요청 저장

스레드 로컬 객체로 요청을 받아서 응답할 때까지 HttpServletRequest, HttpServletResponse 등을 Spring이 관리하는 객체 안에서 사용할 수 있도록 해주는 것들을 이야기한다.

 

③ FlashMap 복원

FlashMap이란 스프링 3.1 버전부터 파라미터를 간편하게 전달하기 위해 추가된 것으로 URL에 데이터를 노출시키지 않으면서 데이터를 전달할 때 사용된다. 이런 FlashMap을 복원한다는 것은 현재 실행이 redirect 되었다고 할 수 있고, redirect는 보통 DB작업을 하면서 값의 변경이 생길 때 사용한다.

DispatcherServlet
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
* for the actual dispatching.
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	
    	...
        
	FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
	if (inputFlashMap != null) {
		request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
	}
	request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
	request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

	try {
		doDispatch(request, response);
	}
        
	...
        
}

 

모든 요청은 HttpServletRequest, HttpServletResponse 객체를 매개변수로 받는 doService()를 호출하게 된다. FlashMap 복원을 한 후에 doDispatch() 메소드에게 위임하는 것을 알 수 있다.

 

DispatcherServlet
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	try {
		ModelAndView mv = null;
		Exception dispatchException = null;

		try {
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);
                
            ...

주석에서 실제로 핸들러에게 요청을 전달하는 처리를 한다고 나와있다. 

 

④ 멀티파트 요청

파일 업로드를 할 때는 다른 Request 객체가 필요하다. 파일 업로드를 원하는 요청이 들어오면 Request 대신에 MultipartResolver가 Multipart를 결정하게 해 준다.

DispatcherServlet
/**
 * Convert the request into a multipart request, and make multipart resolver available.
 * <p>If no multipart resolver is set, simply use the existing request.
 * @param request current HTTP request
 * @return the processed request (multipart wrapper if necessary)
 * @see MultipartResolver#resolveMultipart
 */
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    	...
		else {
			try {
				return this.multipartResolver.resolveMultipart(request);
			}
			...
		}
	}
	// If not returned before: return original request.
	return request;
}

 

⑤ 핸들러 결정과 실행

실제 요청을 처리하는 핸들러를 결정하고 실행한다.

 

2. 요청 전달


출처: https://cphinf.pstatic.net/mooc/20180219_20/1519003954110F9wyd_PNG/4.png

DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	try {
		ModelAndView mv = null;
		Exception dispatchException = null;
		try {
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);

			// Determine handler for the current request.
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null || mappedHandler.getHandler() == null) {
				noHandlerFound(processedRequest, response);
				return;
			}

			// Determine handler adapter for the current request.
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            ...

 

①~② HandlerMapping으로 HandlerExecutionChain결정

HandlerMapping구현체는 어떤 핸들러가 요청을 처리할지에 대한 정보를 알고 있다.

만약에 HandlerExecutionChain을 발견하지 못하면 페이지가 없는 것으로 404 에러를 전달한다.

 

위의 소스코드에서 HandlerExecutionChain으로 선언한 mappedHandler를 getHanlder()하는 부분을 보자.

주석으로 현재 request의 handler를 정한다고 나와있다.

그러면 getHandler()의 소스를 먼저 보자.

DispatcherServlet
/**
 * Return the HandlerExecutionChain for this request.
 * <p>Tries all handler mappings in order.
 * @param request current HTTP request
 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
 */
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	for (HandlerMapping hm : this.handlerMappings) {
		HandlerExecutionChain handler = hm.getHandler(request);
		if (handler != null) {
			return handler;
		}
	}
	return null;
}

HandlerExecutionChain으로 리턴한다고 했는데 먼저 HandlerExecutionChain이 무엇인지 알아보자.

 

org.springframework.web.servlet.HandlerExecutionChain
/**
 * Create a new HandlerExecutionChain.
 * @param handler the handler object to execute
 * @param interceptors the array of interceptors to apply
 * (in the given order) before the handler itself executes
 */
public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors) {
	if (handler instanceof HandlerExecutionChain) {
		HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
		this.handler = originalChain.getHandler();
		this.interceptorList = new ArrayList<HandlerInterceptor>();
		CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
		CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
	}
	else {
		this.handler = handler;
		this.interceptors = interceptors;
	}
}

HandlerExecutionChain구현체는 실제로 호출된 핸들러에 대한 참조를 가지고 있다. 즉, 무엇이 실행되어야 될지 알고 있는 객체라고 말할 수 있으며, 핸들러 실행 전과 실행 후에 수행될 HandlerInterceptor도 참조하고 있다.

간단히 말해 Object타입으로 전달되는 handler를 원래의 Handler 타입에 맞게 변환하여 그 Handler를 전달하는 등의 일을 하고 있다.

다시 getHandler()로 돌아간다.

 

DispatcherServlet
/**
 * Return the HandlerExecutionChain for this request.
 * <p>Tries all handler mappings in order.
 * @param request current HTTP request
 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
 */
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	for (HandlerMapping hm : this.handlerMappings) {
		HandlerExecutionChain handler = hm.getHandler(request);
		if (handler != null) {
			return handler;
		}
	}
	return null;
}

getHandler() 메소드는 for문을 돌면서 DispatcherServlet이 가지고 있는 HandlerMapping객체들의 getHandler() 메소드에 다시 request를 전달하여 request를 처리할 수 있는 HandlerExcutionChain을 찾아 반환하는 역할을 한다.

 

org.springframework.web.servlet.handler.AbstractHandlerMapping
/**
 * Look up a handler for the given request, falling back to the default
 * handler if no specific one is found.
 * @param request current HTTP request
 * @return the corresponding handler instance, or the default handler
 * @see #getHandlerInternal
 */
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
	// Bean name or resolved handler?
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = getApplicationContext().getBean(handlerName);
	}
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	return executionChain;
}

DispatcherServlet이 가지고 있는 HandlerMapping객체들을 getHanlder() 메소드 해주는 소스를 보면 처음에 getHandlerInternal() 메소드를 호출한다.

 

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
/**
 * Look up a handler method for the given request.
 */
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	this.mappingRegistry.acquireReadLock();
	try {
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock();
	}
}

getHandlerInternal() 메소드에서 처음에 lookupPath로 request객체에서 사용자가 요청한 url을 불러온다. 

그 후, lookupHandlerMethod() 메소드에 찾은 url과 request를 전달한다.

 

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
/**
 * Look up the best-matching handler method for the current request.
 * If multiple matches are found, the best match is selected.
 * @param lookupPath mapping lookup path within the current servlet mapping
 * @param request the current request
 * @return the best-matching handler method, or {@code null} if no match
 * @see #handleMatch(Object, String, HttpServletRequest)
 * @see #handleNoMatch(Set, String, HttpServletRequest)
 */
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<Match>();
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	...
}

주석에서 알 수 있듯이 현재 request에서 best-matching 되는 handlerMethod와 매칭 해준다고 나와있다. 

즉, request에서 요구하는 method를 handle 할 수 있는 도구(handler)를 가졌다고 볼 수 있다.

이제 다시 DispatcherServlet의 doDispatch() 메소드 안의 getHandler() 메소드 위에 있는 주석처럼 현재 request의 handler를 구했다고 볼 수 있다.

 

③~④ HandlerAdapter결정

HandlerAdapter는 선택된 핸들러를 실행하는 방법과 응답을 ModelAdapter로 변화하는 방법에 대하여 알고 있다. @RequestMapping과 @Controller어노테이션을 통해 정의되는 컨트롤러의 경우 RequestMappingHandlerMapping이 선택된다. ( DefaultAnnotationHAndlerMapping은 3.1부터 deprecated 되고 RequestMappingHandlerMapping으로 대체됐다.)

DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	try {
		ModelAndView mv = null;
		Exception dispatchException = null;
		try {
			...
			// Determine handler adapter for the current request.
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            ...

 

DispatcherServlet
/**
 * Return the HandlerAdapter for this handler object.
 * @param handler the handler object to find an adapter for
 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
 */
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	for (HandlerAdapter ha : this.handlerAdapters) {
		if (ha.supports(handler)) {
			return ha;
		}
	}
	...
}

 

https://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte2:ptl:handlermapping 

 

egovframework:rte2:ptl:handlermapping [eGovFrame]

DispatcherServlet에 Client로부터 Http Request가 들어 오면 HandlerMapping은 요청처리를 담당할 Controller를 mapping한다. Spring MVC는 interface인 HandlerMapping의 구현 클래스도 가지고 있는데, 용도에 따라 여러 개의

www.egovframe.go.kr

 

3. 요청 처리


출처: https://cphinf.pstatic.net/mooc/20180219_167/1519004040926yL8eC_PNG/5.png

 

① 사용 가능한 인터셉터가 존재

사용 가능한 인터셉터가 존재한다면 preHandle을 호출해서 요청을 처리하고 핸들러가 실행된다.

DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
                
				...
                
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}                     
				...                
			}
       }

applyPreHandle() 메소드를 타고 가본다.

 

org.springframework.web.servlet.HandlerExecutionChain
/**
 * Apply preHandle methods of registered interceptors.
 * @return {@code true} if the execution chain should proceed with the
 * next interceptor or the handler itself. Else, DispatcherServlet assumes
 * that this interceptor has already dealt with the response itself.
 */
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
		for (int i = 0; i < interceptors.length; i++) {
			HandlerInterceptor interceptor = interceptors[i];
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
	}
	return true;
}

주석에 나와있는 것처럼 사용 가능한 인터셉터가 존재하면 preHandle을 호출하여 인터셉터가 없을 때까지 요청을 처리하고 돌아온다.

 

② ModelAndView리턴

ModelAndView는 컨트롤러의 처리 결과를 보여줄 view와 view에서 사용할 값을 전달하는 클래스라고 보면 된다. Servlet에서는 모델을 얻고 그 모델을 JSP로 넘길 때 Request 같은 객체를 이용하여 넘겼는데 Spring에서는 web에 종속적이지 않게 하기 위해서 사용한다.

 

Controller
@Controller
public class controllerName {
	@GetMapping(path = "/review")
	public String review(@RequestParam int id, ModelMap model) {
		model = comment(id, model);
		return "review";
	}
    ...
}

@ResponseBody를 사용하지 않은 경우 return값을 view의 이름으로 인식하여, 해당하는 view를 찾아 Model을 바인딩하여 응답한다.

4. 예외 처리


출처: https://cphinf.pstatic.net/mooc/20180219_26/1519004078279fGdRP_PNG/6.png

① HandlerExceptionResolver에 문의

기본적으로  DispatcherServlet이 DefaultHandlerExceptionResolver를 등록한다. HandlerExceptionResolver는 예외가 던져졌을 때 어떤 핸들러를 실행할 것인지에 대한 정보를 제공한다.

 

5. 뷰 렌더링 과정


출처: https://cphinf.pstatic.net/mooc/20180219_66/1519004113425TanBR_PNG/7.png

① view가 String을 참조

뷰 렌더링 요청이 왔을 때 뷰가 String이라면 ViewResolver의 구현체를 찾지 못했다면 ServletExcption을 발생시킨다.

WebMvcContextConfiguration
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "kr.or.sskey.booking.controller", "kr.or.sskey.booking.apiController" })
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
	@Bean
	public InternalResourceViewResolver getInternalResourceViewResolver() {
		InternalResourceViewResolver resolver = new InternalResourceViewResolver();
		resolver.setPrefix("/WEB-INF/views/");
		resolver.setSuffix(".jsp");
		return resolver;
	}
}

 

6. 요청 처리 종료


https://cphinf.pstatic.net/mooc/20180219_296/1519004150778ofOPV_PNG/8.png

① HandlerExecutionChain이 존재

인터셉터의 afterCompletion메서드를 실행하고 RequestHandleEvent를 발생시킨다.

 

 

출처 & 참고

https://www.boostcourse.org/web316/lecture/254347?isDesc=false

https://seongilman.tistory.com/16

https://galid1.tistory.com/526

https://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte2:ptl:handlermapping

https://pplenty.tistory.com/7

https://ncucu.me/17

 

'공부 정리 > spring' 카테고리의 다른 글

Spring MVC - Dispatcher Servlet(1)  (0) 2021.08.23
JDBC & Spring JDBC  (0) 2020.11.11