Spring源码分析二十二:Spring MVC③ DispatcherServlet的逻辑

一、前言

本文是笔者阅读Spring源码的记录文章,由于本人技术水平有限,在文章中难免出现错误,如有发现,感谢各位指正。在阅读过程中也创建了一些衍生文章,衍生文章的意义是因为自己在看源码的过程中,部分知识点并不了解或者对某些知识点产生了兴趣,所以为了更好的阅读源码,所以开设了衍生篇的文章来更好的对这些知识点进行进一步的学习。


Spring全集目录:Spring源码分析:全集整理


本系列目录如下:

  1. Spring源码分析二十:Spring MVC① 搭建
  2. Spring源码分析二十一:Spring MVC② DispatcherServlet的初始化
  3. Spring源码分析二十二:Spring MVC③ DispatcherServlet的逻辑

衍生篇目录如下:

  1. Spring 源码分析衍生篇十 :Last-Modified 缓存机制
  2. Spring 源码分析衍生篇十一 :HandlerMapping

1. Spring mvc 流程

其流程图如下
在这里插入图片描述
Spring工作流程描述

  1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
  3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)
  4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
    HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
    数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
    数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
    数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
  5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
  6. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
  7. ViewResolver 结合Model和View,来渲染视图
  8. 将渲染结果返回给客户端。

二、HttpServlet & FrameworkServlet

下面我们先看看 DispatcherServlet 的两个父类: HttpServletFrameworkServlet
在这里插入图片描述

FrameworkServlet 重写了 HttpServletservice 方法。 我们这里先来看看 FrameworkServlet#service 方法。

	/**
	 * Override the parent class implementation in order to intercept PATCH requests.
	 */
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
    
    
		// 解析请求方式
		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
    
    
			processRequest(request, response);
		}
		else {
    
    
			super.service(request, response);
		}
	}

这里我们需要关注两个点 : processRequest(request, response);super.service(request, response);。下面我们一一来看

1. HttpServlet#service

我们知道 HttpServlet 类中分别提供了相应的服务方法(doGet、doPost等),如下图
在这里插入图片描述

同时,HttpServlet 会根据请求的不同形式引导到对应导函数中处理,如下 HttpServlet#service(HttpServletRequest, HttpServletResponse)

	protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    
    

        String method = req.getMethod();
		// 如果是 get 方法
        if (method.equals(METHOD_GET)) {
    
    
        	//  lastModified  缓存判断
            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 {
    
    
            	// 如果设置了缓存时间,则判断在ifModifiedSince  之后的时间,是否被修改。
                long ifModifiedSince;
                try {
    
    
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
    
    
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
    
    
                    // 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);
        }
    }

而这几个函数最常用的就是 doGet()doPost() 。这两个方法被 FrameworkServlet 重写了。我们来看看在 FrameworkServlet 中的实现。



	/**
	 * Delegate GET requests to processRequest/doService.
	 * <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
	 * with a {@code NoBodyResponse} that just captures the content length.
	 * @see #doService
	 * @see #doHead
	 */
	@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
    
    

		processRequest(request, response);
	}

	/**
	 * Delegate POST requests to {@link #processRequest}.
	 * @see #doService
	 */
	@Override
	protected final void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
    
    

		processRequest(request, response);
	}
	... 

我们可以很清楚的看到,对于大部分的请求,还是依赖于 HttpServlet#service(HttpServletRequest, HttpServletResponse) 来进行一个请求的分发。对于我们常见的 doGet() 和 doPost() 方法都是直接调用 processRequest(request, response);, 而processRequest方法 的具体实现在FrameworkServlet#processRequest 中 。

2. FrameworkServlet#processRequest

因此。接下来我们就来看看 org.springframework.web.servlet.FrameworkServlet#processRequest

	 */
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
    
    
		// 记录当前时间,用于记录web请求的记录时间
		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;
		// 1234 的目的是为了保证当前线程的 LocaleContext 和 RequestAttributes 在当前请求后还能恢复,所以提取保存
		// 1 提取当前线程的 LocaleContext  属性
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		// 2. 根据当前的request 创建对应的 LocaleContext ,并绑定到当前线程
		LocaleContext localeContext = buildLocaleContext(request);
		// 3. 提取当前线程的 RequestAttributes 属性
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		// 根据当前的request 创建对应的 RequestAttributes ,并绑定到当前线程
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

		initContextHolders(request, localeContext, requestAttributes);

		try {
    
    
			// 5. 委托给 doservice方法进行进一步处理
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
    
    
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
    
    
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
    
    
			// 6. 请求结束,恢复线程原状
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
    
    
				requestAttributes.requestCompleted();
			}
			logResult(request, response, failureCause, asyncManager);
			// 发布请求结束的通知事件,无论成功与否
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

由于逻辑都被封装到 doService(request, response); 中,所以这里还是比较简单。而doService又被 DispatcherServlet实现了。因此我们这里来看看 DispatcherServlet#doService

3. DispatcherServlet#doService

	/**
	 * 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 {
    
    
		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.
		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.
		// 设置一些Spring 上下文
		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());

		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 {
    
    
			// 真正的业务逻辑地方
			doDispatch(request, response);
		}
		finally {
    
    
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    
    
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
    
    
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

很显然,这 “爷仨” 一层一层传递,终于传递到了DispatcherServlet 手里,这里我们直接开始看doDispatch(request, response);,这里才是核心逻辑的所在。

三、DispatcherServlet#doDispatch

	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 {
    
    
				// 1. 如果是 MultipartContent 类型的request 则转换request 为 MultipartHttpServletRequest 类型的request
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// 2. 根据request 寻找对应的 handler
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
    
    
					// 3. 如果没有找到对应的handler,则通过 response 反馈错误信息
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				// 4. 根据当前的 handler 找到对应的HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				// 5. last-modified 的缓存处理 
				// 如果当前handler 支持 last-modified 头处理则进行缓存处理
				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;
					}
				}
				// 6.1 拦截器方法的调用
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    
    
					return;
				}

				// Actually invoke the handler.
				// 7. 真正调用 handler 并 返回视图
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				
				if (asyncManager.isConcurrentHandlingStarted()) {
    
    
					return;
				}
				// 8. 视图名称转换应用于需要添加前缀的情况
				applyDefaultViewName(processedRequest, mv);
				// 6.2 拦截器后置方法的调用
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
    
    
				// 记录下来异常,在 9 中统一处理
				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.
				// 记录下来异常,在 9 中统一处理
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			// 9. 处理最后的结果
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
    
    
			// 6.3 拦截器完成方法的调用
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
    
    
			// 6.3 拦截器完成方法的调用
			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#doDispatch 涉及的地方就比较多,下面我们一个一个看:

1. checkMultipart(request)

对于请求的处理,Spring首先考虑的是对 Multipart 的处理,如果是 MultipartContent 类型的request 则转换request 为 MultipartHttpServletRequest 类型的request。简单来说,就是判断是否是文件请求。

	protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    
    
		if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
    
    
			if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
    
    
				if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
    
    
					logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
				}
			}
			else if (hasMultipartException(request)) {
    
    
				logger.debug("Multipart resolution previously failed for current request - " +
						"skipping re-resolution for undisturbed error rendering");
			}
			else {
    
    
				try {
    
    
					return this.multipartResolver.resolveMultipart(request);
				}
				catch (MultipartException ex) {
    
    
					if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
    
    
						logger.debug("Multipart resolution failed for error dispatch", ex);
						// Keep processing error dispatch with regular request handle below
					}
					else {
    
    
						throw ex;
					}
				}
			}
		}
		// If not returned before: return original request.
		return request;
	}

2. getHandler(processedRequest);

这一步的目的是 根据request 信息遍历 HandlerMapping 找到对应的handler。
具体代码如下:

	// DispatcherServlet#getHandler
	@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    
    
		// 这里的 this.handlerMappings 在没有手动调整的情况下是加载的默认配置文件中的数据
		if (this.handlerMappings != null) {
    
    
			// 遍历每一个 handleMapping,解析 request,直到碰到一个解析成功的,将解析后的 Handler拦截链路返回。
			for (HandlerMapping mapping : this.handlerMappings) {
    
    
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
    
    
					return handler;
				}
			}
		}
		return null;
	}

关于 mapping.getHandler(request) 的处理。这一部分参考衍生篇 Spring 源码分析衍生篇十一 :HandlerMapping。里面有关于 HandlerMapping 详细的分析。

3. noHandlerFound(processedRequest, response);

正常情况下,每一个请求都应该对应一个 Handler,因为每个请求都应该在后台有对应的处理逻辑。而逻辑的实现就是在Handler 中。正常情况下,如果没有URL匹配的Handler,我们可以通过设置默认的Handler 来解决这一问题,不过如果没有设置默认的Handler。则只能通过Response 向用户返回错误信息。

	protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		if (pageNotFoundLogger.isWarnEnabled()) {
    
    
			pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
		}
		// 判断DispatcherServlet 属性设置,是否需要抛出异常
		if (this.throwExceptionIfNoHandlerFound) {
    
    
			throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
					new ServletServerHttpRequest(request).getHeaders());
		}
		else {
    
    
			// 否则直接抛出错误 404
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
		}
	}

4. getHandlerAdapter(mappedHandler.getHandler());

这一步的目的是根据 Handler 寻找对应的 HandlerAdapter。这里使用了适配器模式,遍历所有的 Adapter。根据 HandlerAdapter#supports 方法来判断是否支持当前Handler 的解析,如果支持,则返回。

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

我们这里返回的是RequestMappingHandlerAdapter,其判定条件如下:

	@Override
	public final boolean supports(Object handler) {
    
    
		// 	supportsInternal((HandlerMethod) handler)) 返回 true
		return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
	}

5. Last-Modified 的缓存处理

  1. 在客户端第一次输入 URL 时,服务器端返回内容和200状态码,表示请求成功,同时会添加一个 “Last-Modified” 的响应头,表示此文件在服务器上最后的更新时间。

  2. 客户端第二次请求此URL时2,客户端会向服务器发送请求头 “If-Modified-Since”,询问服务器该时间之后当前请求是否有被修改过,如果服务端内容没有变化,则会自动返回 304 状态码(只要响应头,内容为空,这样就节省了带宽)。

    Spring 实现 Last-Modified 机制,只需要实现 LastModified 接口就可以。如下:

    @Component
    public class BeanNameSayController implements Controller, LastModified {
          
          
        private long lastModified;
        
        @Override
        public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
          
          
            return new ModelAndView("/hello");
        }
        
        @Override
        public long getLastModified(HttpServletRequest request) {
          
          
            if (lastModified == 0L){
          
          
                lastModified = System.currentTimeMillis();
            }
            return lastModified;
        }
    }
    

具体请看衍生篇 :Spring 源码分析衍生篇十 :Last-Modified 缓存机制

6. 拦截器的调用

我们添加的拦截器,会在下面这些地方被调用合适的方法。

6.1 mappedHandler.applyPreHandle(processedRequest, response)

逻辑很简单,遍历所有的拦截器,分别调用 preHandle 前置方法。

	// org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
	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];
				// 调用前置方法。如果有一个前置方法返回false,则直接调用完成方法
				if (!interceptor.preHandle(request, response, this.handler)) {
    
    
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}

6.2. mappedHandler.applyPostHandle(processedRequest, response, mv);

逻辑基本相同,没有什么区别,这里调用的是程序执行后的后置方法

	// org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle
	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
			throws Exception {
    
    

		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
    
    
			for (int i = interceptors.length - 1; i >= 0; i--) {
    
    
				HandlerInterceptor interceptor = interceptors[i];
				// 调用后置方法
				interceptor.postHandle(request, response, this.handler, mv);
			}
		}
	}

6.3 triggerAfterCompletion(processedRequest, response, mappedHandler, ex);

调用拦截器结束方法(视图呈现之后)。

	// org.springframework.web.servlet.DispatcherServlet#triggerAfterCompletion
	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
			throws Exception {
    
    

		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
    
    
			for (int i = this.interceptorIndex; i >= 0; i--) {
    
    
				HandlerInterceptor interceptor = interceptors[i];
				try {
    
    
					// 调用结束方法
					interceptor.afterCompletion(request, response, this.handler, ex);
				}
				catch (Throwable ex2) {
    
    
					logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
				}
			}
		}
	}

7. ha.handle(processedRequest, response, mappedHandler.getHandler());

这里是真正调用 Handler 处理业务逻辑的地方。我们这里看的是 RequestMappingHandlerAdapterha.handle(processedRequest, response, mappedHandler.getHandler()); 方法会调用 RequestMappingHandlerAdapter#invokeHandlerMethod 方法。同时在这个方法里面,会通过反射的方式调用 HandlerMthoder。并将返回结果封装成 ModelAndView

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
    
    
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
			// 将 handlerMethod 转换成 ServletInvocableHandlerMethod 
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			// 设置ServletInvocableHandlerMethod  的一些属性
			if (this.argumentResolvers != null) {
    
    
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
    
    
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
			// 创建 ModelAndView 容器,并设置一些属性
			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

			AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
			asyncWebRequest.setTimeout(this.asyncRequestTimeout);

			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
			asyncManager.setTaskExecutor(this.taskExecutor);
			asyncManager.setAsyncWebRequest(asyncWebRequest);
			asyncManager.registerCallableInterceptors(this.callableInterceptors);
			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

			if (asyncManager.hasConcurrentResult()) {
    
    
				Object result = asyncManager.getConcurrentResult();
				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
				asyncManager.clearConcurrentResult();
				LogFormatUtils.traceDebug(logger, traceOn -> {
    
    
					String formatted = LogFormatUtils.formatValue(result, !traceOn);
					return "Resume with async result [" + formatted + "]";
				});
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}
			// 反射调用 HandlerMethod
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
    
    
				return null;
			}
			// 封装成 ModelAndView 
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
    
    
			webRequest.requestCompleted();
		}
	}

8. applyDefaultViewName(processedRequest, mv);

当 控制层的返回结果是 null 或者 void 时,则表明没有找到对应视图,Spring 会根据request 信息来进行解析,查找默认的视图。

	private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
    
    
		// 视图转换器应用于添加前后缀的情况
		if (mv != null && !mv.hasView()) {
    
    
			String defaultViewName = getDefaultViewName(request);
			if (defaultViewName != null) {
    
    
				mv.setViewName(defaultViewName);
			}
		}
	}

9. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

这一步的作用是集中处理请求异常并解析最终的ModelAndView,根据解析出来的视图跳转页面。

	// org.springframework.web.servlet.DispatcherServlet#processDispatchResult
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {
    
    

		boolean errorView = false;
		// 判断上面的过程是否出现了异常。如果本次请求出现了异常,并不能因此终止程序。所以需要解析出来异常,返回对应的视图结果,告知用户出现了异常。
		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);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render?
		// 如果在Handler实例的处理过程中返回了 view,则需要做页面处理
		if (mv != null && !mv.wasCleared()) {
    
    
			// 处理页面跳转
			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);
		}
	}

9.1 render(mv, request, response);

在最后的处理中,一定会涉及页面的跳转问题。而在render(mv, request, response); 完成了页面的跳转。

	// org.springframework.web.servlet.DispatcherServlet#render
	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();
		// 如果viewname不为null,则需要通过viewName 解析出来对应的 View
		if (viewName != null) {
    
    
			// We need to resolve the view name.
			// 解析视图名称
			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 {
    
    
			// 如果viewName 为null,则认为 ModelAndView 直接指定了View。不需要解析了。
			// 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 {
    
    
			// 设置视图状态。可能是 404, 500 等情况
			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;
		}
	}

resolveViewName(viewName, mv.getModelInternal(), locale, request); 是 通过视图解析器进行视图解析。返回合适视图。具体实现如下。

	// org.springframework.web.servlet.DispatcherServlet#resolveViewName
	@Nullable
	protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
			Locale locale, HttpServletRequest request) throws Exception {
    
    

		if (this.viewResolvers != null) {
    
    
			// 遍历视图解析器,直到有解析器能解析出来视图
			for (ViewResolver viewResolver : this.viewResolvers) {
    
    
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
    
    
					return view;
				}
			}
		}
		return null;
	}

我们看一下 viewResolver.resolveViewName(viewName, locale) 方法。这里我们看InternalResourceViewResolver#resolveViewName 方法,其方法是在父类AbstractCachingViewResolver#resolveViewName中实现,如下:

	@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
    
    
		// 如果没有缓存,则直接创建 View
		if (!isCache()) {
    
    
			return createView(viewName, locale);
		}
		else {
    
    
			// 从缓存中获取视图
			Object cacheKey = getCacheKey(viewName, locale);
			View view = this.viewAccessCache.get(cacheKey);
			if (view == null) {
    
    
				synchronized (this.viewCreationCache) {
    
    
					view = this.viewCreationCache.get(cacheKey);
					if (view == null) {
    
    
						// Ask the subclass to create the View object.
						view = createView(viewName, locale);
						if (view == null && this.cacheUnresolved) {
    
    
							view = UNRESOLVED_VIEW;
						}
						if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
    
    
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
						}
					}
				}
			}
			else {
    
    
				if (logger.isTraceEnabled()) {
    
    
					logger.trace(formatKey(cacheKey) + "served from cache");
				}
			}
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}

createView 方法 被 UrlBasedViewResolver 重写了。UrlBasedViewResolver#createView具体如下:

	@Override
	protected View createView(String viewName, Locale locale) throws Exception {
    
    
		// If this resolver is not supposed to handle the given view,
		// return null to pass on to the next resolver in the chain.
		// 如果当前视图解析器无法解析该视图,则返回null
		if (!canHandle(viewName, locale)) {
    
    
			return null;
		}

		// Check for special "redirect:" prefix.
		// 处理前缀为  "redirect:" (重定向)的情况
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
    
    
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
			RedirectView view = new RedirectView(redirectUrl,
					isRedirectContextRelative(), isRedirectHttp10Compatible());
			String[] hosts = getRedirectHosts();
			if (hosts != null) {
    
    
				view.setHosts(hosts);
			}
			return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
		}

		// Check for special "forward:" (请求转发)prefix.
		// 处理前缀为  "forward:" 的情况
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
    
    
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
			InternalResourceView view = new InternalResourceView(forwardUrl);
			return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
		}

		// Else fall back to superclass implementation: calling loadView.
		// 调用父类的方法创建视图
		return super.createView(viewName, locale);
	}

super.createView(viewName, locale); 调用 AbstractCachingViewResolver#createView 如下:

	// org.springframework.web.servlet.view.AbstractCachingViewResolver#createView
	protected View createView(String viewName, Locale locale) throws Exception {
    
    
		return loadView(viewName, locale);
	}
	
	// org.springframework.web.servlet.view.UrlBasedViewResolver#loadView
	@Override
	protected View loadView(String viewName, Locale locale) throws Exception {
    
    
		AbstractUrlBasedView view = buildView(viewName);
		View result = applyLifecycleMethods(viewName, view);
		return (view.checkResource(locale) ? result : null);
	}
	
	// org.springframework.web.servlet.view.UrlBasedViewResolver#buildView
	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    
    
		Class<?> viewClass = getViewClass();
		Assert.state(viewClass != null, "No view class");

		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
		// 设置视图 url 添加前缀和后缀
		view.setUrl(getPrefix() + viewName + getSuffix());
		view.setAttributesMap(getAttributesMap());

		String contentType = getContentType();
		if (contentType != null) {
    
    
			view.setContentType(contentType);
		}

		String requestContextAttribute = getRequestContextAttribute();
		if (requestContextAttribute != null) {
    
    
			view.setRequestContextAttribute(requestContextAttribute);
		}

		Boolean exposePathVariables = getExposePathVariables();
		if (exposePathVariables != null) {
    
    
			view.setExposePathVariables(exposePathVariables);
		}
		Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
		if (exposeContextBeansAsAttributes != null) {
    
    
			view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
		}
		String[] exposedContextBeanNames = getExposedContextBeanNames();
		if (exposedContextBeanNames != null) {
    
    
			view.setExposedContextBeanNames(exposedContextBeanNames);
		}

		return view;
	}

org.springframework.web.servlet.view.AbstractView#render 中完成了视图跳转。
对于ModelView 的使用,我们可以将一些属性放入其中,然后在页面上通过 JSTL 语法或者 request 获取属性,这个功能的实现就是在这里完成的。实现原理很简单,就是将要用到的属性方法request中,以便在其他地方可以获取到。

	@Override
	public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
    
    

		if (logger.isDebugEnabled()) {
    
    
			logger.debug("View " + formatViewName() +
					", model " + (model != null ? model : Collections.emptyMap()) +
					(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
		}
		// 将要用到的属性放入到mergedModel  中
		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		// 准备 Response
		prepareResponse(request, response);
		// 处理页面跳转。同时将 mergedModel  保存到request中
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}

以上:内容部分参考
《Spring源码深度解析》
https://www.jianshu.com/p/1bff57c74037
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

猜你喜欢

转载自blog.csdn.net/qq_36882793/article/details/109175204