天天看點

spring-web DispatcherServlet 源碼分析

說明

  1. 本文基于 jdk 8, spring-framework 5.2.x 編寫。
  2. @author JellyfishMIX - github / blog.jellyfishmix.com
  3. LICENSE GPL-2.0

DispatcherServlet 的繼承實作層次

關注點應放在 Servlet, GenericServlet, HttpServlet, HttpServletBean, FrameworkServlet, DispatcherServlet

spring-web DispatcherServlet 源碼分析

Servlet

javax.servlet.Servlet

package javax.servlet;

public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    public String getServletInfo();

    public void destroy();
}
           

Servlet 接口提供處理請求的能力。請求來自于 web 容器,例如 tomcat, JBoss, Jetty 等。spring-web 應用和 web 容器共同遵守 Servlet 規範。Servlet 位于 javax.servlet 包内,不是 spring 的領域。

GenericServlet

javax.servlet.GenericServlet

package javax.servlet;

public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {
	public void init(ServletConfig config) throws ServletException {
		this.config = config;
		this.init();
	}
}
           

GenericServlet 沒有實作 service 方法,沒有處理請求的邏輯。實作了 init 方法,但是個模闆方法,由子類實作,例如 spring 的 HttpServletBean 就實作了該模闆方法。GenericServlet 位于 javax.servlet 包内,不是 spring 的領域。

HttpServlet

javax.servlet.http.HttpServlet

  1. 基于 Servlet 規範的應用可以響應所有類型的請求(不僅僅是 HTTP,也可以是 WebSocket, FTP 等),但通常用來處理 HTTP 請求。
  2. Servlet 接口有一個非常重要的實作類 HttpServlet,實作了 service 方法,SpringMVC 架構中的 DispatcherServlet 就是繼承自 HttpServlet。HttpServlet 位于 javax.servlet.http 包内,不是 spring 的領域。
package javax.servlet.http;

public abstract class HttpServlet extends GenericServlet {
}
           

HttpServlet#service 方法

javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)

  1. 轉換為 HttpServletRequest 和 HttpServletResponse。
  2. 判斷是何種類型的 http 請求,調用對應的 doXX 方法。GET 請求調用 doGet,POST 請求調用 doPost。doXX 方法是 HttpServlet 接口定義的。
@Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }
        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
        } else {
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }
           

FrameworkServlet

org.springframework.web.servlet.FrameworkServlet

  1. FrameworkServlet 在 spring 的包中,進入到 spring 的領域。
  2. FrameworkServlet 重寫了 doGet(), doPost() 等 doXX 方法,doXX 方法調用了 processRequest() 方法。
package org.springframework.web.servlet;

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
}
           

FrameworkServlet#processRequest 方法

org.springframework.web.servlet.FrameworkServlet#processRequest

  1. i18n 國際化設定。
  2. 建立 ServletRequestAttributes 對象,初始化上下文 holders,将 request, response 對象放入到線程上下文中。
  3. 調用 doService() 方法,處理請求。核心方法,由子類實作。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;
		// i18n 國際化設定
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		LocaleContext localeContext = buildLocaleContext(request);
		// 建立 ServletRequestAttributes 對象,初始化上下文 holders,将 request, response 對象放入到線程上下文中
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
		initContextHolders(request, localeContext, requestAttributes);
        
		try {
			// 處理請求,核心方法,由子類實作
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
            // ...
		}
		catch (Throwable ex) {
            // ...
		} finally {
            // ...
		}
	}
           

FrameworkServlet#initServletBean 方法

org.springframework.web.servlet.FrameworkServlet#initServletBean

  1. FrameworkServlet 還實作了 HttpServletBean 提供的模版方法 initServletBean,初始化 WebApplicationContext。調用 initFrameworkServlet 模闆方法,供子類實作初始化工作。
protected final void initServletBean() throws ServletException {
        // 日志代碼移除,保留核心代碼
		// 初始化 WebApplicationContext
		this.webApplicationContext = initWebApplicationContext();
        // 模闆方法,供子類實作初始化工作
		initFrameworkServlet();
	}
           

DispatcherServlet#doService 方法

org.springframework.web.servlet.DispatcherServlet#doService

  1. 首先判斷是不是 include 請求,如果是則對 request 的 attribute 做個快照備份。
  2. 為 request 設定 WebApplicationContext, 國際化 i18n 相關 resolver, theme 主題相關 resolver
  3. 重定向屬性處理,對 redirect 請求的支援。
  4. 調用 DispatcherServlet#doDispatch 方法,核心方法,請求分發給對應的 handler 并處理。
@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		logRequest(request);

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		// 首先判斷是不是 include 請求,如果是則對 request 的 attribute 做個快照備份
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		// 為 request 設定 WebApplicationContext
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		// 為 request 設定國際化 i18n 相關 resolver
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		// 為 reqeust 設定 theme 主題相關 resolver
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		// 重定向屬性處理,對 redirect 請求的支援
		if (this.flashMapManager != null) {
			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 {
			// 核心方法,請求分發給對應的 handler 并處理
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				// doDispatch 方法執行完後,如果不是異步調用且未完成,對已備份好的快照進行還原,在做完快照後又對 request 設定了一些屬性
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}
           

DispatcherServlet#doDispatch

org.springframework.web.servlet.DispatcherServlet#doDispatch

  1. 先檢查請求是不是 multipart 請求,如果是則傳回已處理的 request,否則不對 request 做額外處理。
  2. 周遊 HandlerMappings 集合,根據 HandlerMapping 擷取 HandlerExecutionChain,即 mappedHandler。
  3. 根據 mappedHandler 擷取适配的 HandlerAdapter。
  4. 以責任鍊模式調用 HandlerInterceptor 攔截器的 preHandle 方法(自定義的 spring 攔截器調用位置)。
  5. 調用 HandlerAdapter#handle 方法,處理請求。實際 Controller 最終在這裡面被調用,處理完後會傳回 ModelAndView。
  6. 将 HttpServletRequest 請求的 uri 轉換成 ViewName 字元串并設定到 ModelAndView 中。
  7. 以責任鍊模式調用 HandlerInterceptor 攔截器的 postHandle 方法(自定義的 spring 攔截器調用位置)。
  8. 渲染 view 視圖,調用攔截器的 afterCompletion() 方法。
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 {
				// 先檢查請求是不是 multipart 請求,如果是則傳回已處理的 request,否則不對 request 做額外處理
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// 周遊 HandlerMappings 集合,根據 HandlerMapping 擷取 HandlerExecutionChain,即 mappedHandler
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				// 根據 mappedHandler 擷取适配的 HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				// 以責任鍊模式調用 HandlerInterceptor 攔截器的 preHandle 方法(自定義的 spring 攔截器調用位置)
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				// 調用 HandlerAdapter#handle 方法,處理請求。實際 Controller 最終在這裡面被調用,處理完後會傳回 ModelAndView。
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				// 将 HttpServletRequest 請求的 uri 轉換成 ViewName 字元串并設定到 ModelAndView 中
				applyDefaultViewName(processedRequest, mv);
				// 以責任鍊模式調用 HandlerInterceptor 攔截器的 postHandle 方法(自定義的 spring 攔截器調用位置)
				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 = new NestedServletException("Handler dispatch failed", err);
			}
			// 渲染 view 視圖,調用攔截器的 afterCompletion() 方法
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("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);
				}
			}
		}
	}
           

DispatcherServlet#getHandler 方法

org.springframework.web.servlet.DispatcherServlet#getHandler

  1. 周遊 handlerMapping 從中獲得 HandlerExecutionChain
  2. DispatcherServlet 初始化時注冊的 handlerMapping, @see DispatcherServlet#initStrategies
@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		// DispatcherServlet 初始化時注冊的 handlerMapping, @see DispatcherServlet#initStrategies
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}
           

DispatcherServlet#getHandlerAdapter 方法

org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter

根據 mappedHandler 擷取适配的 HandlerAdapter。

  1. 根據 HandlerExecutionChain 擷取适配的 HandlerAdapter。責任鍊模式,周遊 HandlerAdapter 中調用 HandlerAdapter#support() 方法,判斷目前 HandlerAdapter 是否支援該處理器,支援就傳回該 HandlerAdapter。
  2. DispatcherServlet 初始化時注冊的 HandlerAdapter, @see DispatcherServlet#initStrategies
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				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");
	}
           

DispatcherServlet#applyDefaultViewName 方法

org.springframework.web.servlet.DispatcherServlet#applyDefaultViewName

将 HttpServletRequest 請求的 uri 轉換成 ViewName 字元串并設定到 ModelAndView 中

  1. 判斷 ModelAndView 是否需要 view 視圖層。
  2. getDefaultViewName 方法使用了 RequestToViewNameTranslator,将 HttpServletRequest 請求的 uri 轉換成 ViewName 字元串并設定到 ModelAndView 中。
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
		// 判斷 ModelAndView 是否需要 view 視圖層
        if (mv != null && !mv.hasView()) {
            // getDefaultViewName 方法使用了 RequestToViewNameTranslator,将 HttpServletRequest 請求的 uri 轉換成 ViewName 字元串并設定到 ModelAndView 中
			String defaultViewName = getDefaultViewName(request);
			if (defaultViewName != null) {
				mv.setViewName(defaultViewName);
			}
		}
	}

	@Nullable
	protected String getDefaultViewName(HttpServletRequest request) throws Exception {
		return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
	}

	@Override
	public String getViewName(HttpServletRequest request) {
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
		return (this.prefix + transformPath(lookupPath) + this.suffix);
	}
           

DispatcherServlet#processDispatchResult 方法 – 處理 Handler 執行後的結果

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

處理 Handler 執行後的結果,将 ModelAndView 或者 Exception 渲染成視圖,然後調用 render() 生成頁面。

  1. handler 執行後如果存在 Exception,責任鍊模式調用 HandlerExceptionResolver,将 Exception 處理成 ModelAndView。
  2. 如果 ModelAndView 不為空,調用 render 方法生成頁面。
  3. 以責任鍊模式調用 HandlerInterceptor#afterCompletion() 方法。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;
		// handler 執行後如果存在 Exception,将 Exception 處理為 ModelAndView
		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				// 責任鍊模式調用 HandlerExceptionResolver,将 Exception 處理成 ModelAndView
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			// 如果 ModelAndView 不為空,調用 render 方法生成頁面
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
        // ...

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		// 以責任鍊模式調用 HandlerInterceptor#afterCompletion() 方法
		if (mappedHandler != null) {
			// Exception (if any) is already handled..
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}
           

DispatcherServlet#render 方法 – 生成 View

org.springframework.web.servlet.DispatcherServlet#render

  1. 責任鍊模式調用 ViewResolver#resolveViewName 方法,生成 View
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Determine locale for request and apply it to the response.
		Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
		response.setLocale(locale);

		View view;
		String viewName = mv.getViewName();
		if (viewName != null) {
			// We need to resolve the view name.
			// 責任鍊模式調用 ViewResolver#resolveViewName 方法,生成 View
			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
			if (view == null) {
				throw new ServletException("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) {
				throw new ServletException("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.render(mv.getModelInternal(), request, response);
		} catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error rendering view [" + view + "]", ex);
			}
			throw ex;
		}
	}