1. Spring mvc 流程
其时序图:
整体流程图:
SpringMVC 工作流程描述:
- 用户向服务器发送请求,请求被Spring 前端控制Servelt
DispatcherServlet
捕获; DispatcherServlet
对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping
获得该Handler
配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain
对象的形式返回;DispatcherServlet
根据获得的Handler
,选择一个合适的HandlerAdapter
。(附注:如果成功获得HandlerAdapter
后,此时将开始执行拦截器的preHandler(…)方法)- 提取
Request
中的模型数据,填充Handler
入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:- HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
- 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
- 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
- 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
- Handler执行完成后,向
DispatcherServlet
返回一个ModelAndView
对象; - 根据返回的
ModelAndView
,选择一个适合的ViewResolver
(必须是已经注册到Spring容器中的ViewResolver
)返回给DispatcherServlet
; ViewResolver
结合Model
和View
,来渲染视图- 将渲染结果返回给客户端。
2. HttpServlet & FrameworkServlet
下面我们先看看 DispatcherServlet
的两个父类: HttpServlet
和 FrameworkServlet
。
FrameworkServlet
重写了 HttpServlet
的 service
方法。 我们这里先来看看 FrameworkServlet#service
方法。
@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);
。下面我们一一来看
2.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
中的实现。
@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); } ... 复制代码
我们可以很清楚的看到,对于大部分的请求,还是依赖于 HttpServlet#service(HttpServletRequest, HttpServletResponse)
来进行一个请求的分发。对于我们常见的 doGet()
和 doPost()
方法都是直接调用 processRequest(request, response);
, 而 processRequest
方法 的具体实现在 FrameworkServlet#processRequest
中 。
2.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(); // 4. 根据当前的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 { // todo 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
。
- DispatcherServlet#doService
@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. // 基本的东西保存到request作用域中 方便获取, 设置一些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); } RequestPath previousRequestPath = null; if (this.parseRequestPath) { previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE); ServletRequestPathUtils.parseAndCache(request); } try { // todo 派发功能 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); } } if (this.parseRequestPath) { ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request); } } } 复制代码
很显然,这 “爷仨” 一层一层传递,终于传递到了DispatcherServlet 手里,这里我们直接开始看 doDispatch(request, response);
,这里才是核心逻辑的所在。
3. DispatcherServlet#doDispatch
// SpringMVC处理请求的核心流程 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; // handler (目标方法)的执行链 HandlerExecutionChain mappedHandler = null; // 文件上传标志 boolean multipartRequestParsed = false; // 对异步请求的支持(Servlet3.0 以后才有的,Webflux) 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 // todo 构造出了【目标方法+拦截器整个链路】 决定使用哪个Handler处理当前请求 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { // 3. 如果没有找到对应的handler,则通过 response 反馈错误信息 // 如果找不到处理,就报404错误 noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. // 4. 根据当前的 handler 找到对应的HandlerAdapter // todo 获取适配器 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 = HttpMethod.GET.matches(method); // 如果是 get请求或者 head 请求则进入该分支 if (isGet || HttpMethod.HEAD.matches(method)) { // todo 调用 HandlerAdapter#getLastModified 方法 来获取最后修改时间 long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); // todo 判断到目前为止是否有过修改,没有则直接return。实现缓存的功能 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 6.1 执行 所有拦截器 的preHandle 方法,使用mappedHandler整个链 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. // todo 7. 真正执行目标方法,mappedHandler.getHandler() 并 返回视图 // 反射执行目标方法,确定参数值,处理返回值【封装成ModelAndView】 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } // // 8. 视图名称转换应用于需要添加前缀的情况 默认的ViewName applyDefaultViewName(processedRequest, mv); // 6.2 执行 所有拦截器 的postHandle 方法,使用mappedHandler整个链 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. dispatchException = new NestedServletException("Handler dispatch failed", err); } // todo 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
涉及的地方就比较多,下面我们一个一个看:
3.1 checkMultipart(request)
对于请求的处理,Spring首先考虑的是对 Multipart
的处理,如果是 MultipartContent
类型的request 则转换request 为 MultipartHttpServletRequest
类型的request。简单来说,就是判断是否是文件请求。
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { // multipartResolver 文件上传解析器 isMultipart方法 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { if (DispatcherType.REQUEST.equals(request.getDispatcherType())) { 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; } 复制代码
核心方法 this.multipartResolver.isMultipart(request)
,我们来看一 StandardServletMultipartResolver#isMultipart
:
@Override public boolean isMultipart(HttpServletRequest request) { // 所有文件上传请求都会有这个 return StringUtils.startsWithIgnoreCase(request.getContentType(), (this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/")); } 复制代码
3.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)
的处理。后面详细介绍。
3.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); } } 复制代码
3.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)); } 复制代码
3.5 Last-Modified 的缓存处理
在客户端第一次输入 URL 时,服务器端返回内容和200状态码,表示请求成功,同时会添加一个 “Last-Modified” 的响应头,表示此文件在服务器上最后的更新时间。
客户端第二次请求此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; } 复制代码
}
后面具体分析。
3.6 拦截器的调用
我们添加的拦截器,会在下面这些地方被调用合适的方法。
3.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; } 复制代码
3.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); } } } 复制代码
3.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); } } } } 复制代码
3.7 ha.handle(processedRequest, response, mappedHandler.getHandler());
这里是真正调用 Handler 处理业务逻辑的地方。我们这里看的是 RequestMappingHandlerAdapter
。 ha.handle(processedRequest, response, mappedHandler.getHandler());
方法会调用 RequestMappingHandlerAdapter#invokeHandlerMethod
方法。同时在这个方法里面,会通过反射的方式调用 HandlerMthoder
。并将返回结果封装成 ModelAndView
。
@Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { // 装饰器模式,把原生的request, response 封装到一个对象中 方便后续只用这一个参数就可以 ServletWebRequest webRequest = new ServletWebRequest(request, response); try { // 数据绑定器 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); // 获取到模型工厂 Model(要交给页面的数据) View(我们要去的视图) ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 将 handlerMethod 转换成 ServletInvocableHandlerMethod ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); // 设置ServletInvocableHandlerMethod 的一些属性 // todo 参数解析器 if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } // todo 返回值解析器 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); } // todo 开始执行目标方法,反射调用HandlerMethod invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } // todo 封装ModelAndView return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } 复制代码
我们首先来看下 核心方法 invocableMethod.invokeAndHandle(webRequest, mavContainer);
:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // todo 目标方法的反射执行 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { // todo 返回值的处理 this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } } 复制代码
3.7.1 请求参数解析器argumentResolvers
核心方法 invokeForRequest
中,
@Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // todo 获取方法的 请求参数 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } // 反射执行 return doInvoke(args); } protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 拿到方法的所有参数 MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } // 准备args的数组,跟方法的参数长度一样,挨个确定每个参数都是什么值 Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); // 先去已提供的参数里面查找 args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // todo 解析参数 if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; } private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { // 先看缓存中有没有 HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { // 27个参数解析器 遍历执行 for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { // if (resolver.supportsParameter(parameter)) { result = resolver; // 支持这种参数的解析器也会被放到缓存中argumentResolverCache this.argumentResolverCache.put(parameter, result); break; } } } return result; } 复制代码
一共有27种请求参数解析器,针对不同的参数进行使用不同的解析器。
执行目标方法就是通过反射执行的。
3.7.2 返回值参数解析器
this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); 复制代码
@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { // todo 找到合适的返回值处理器 HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } // 处理返回值 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } @Nullable private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) { boolean isAsyncValue = isAsyncReturnValue(value, returnType); for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) { if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) { continue; } if (handler.supportsReturnType(returnType)) { return handler; } } return null; } 复制代码
一共有15种返回值解析器。
有一个 RequestResponseBodyMethodProcessor
,只要你标注了@ResponseBody注解,使用这个处理器进行处理,将返回值数据直接返回。
3.7.3 封装ModelAndView
getModelAndView(mavContainer, modelFactory, webRequest); @Nullable private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { // modelFactory 准备模型数据,请求域数据共享,session里面的数据存储到请求域中 modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; } ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus()); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (request != null) { // todo 重定向数据的共享 RedictView ,先把数据转移到request,再把request转移到session中 RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } } return mav; } 复制代码
3.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); } } } 复制代码
3.9 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
这一步的作用是 集中处理请求异常并解析最终的ModelAndView,根据解析出来的视图跳转页面
。
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); // todo 处理异常的情况 // 如果所有异常都不能处理,这个异常就直接抛出去 mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // 上面所有的异常解析器都没能处理这个异常,就不执行下面的逻辑了。。 // Did the handler return a view to render? // 动态策略 // @ResponseBody(提前在解析返回值的时候,就已经把数据写出去了) // 如果在Handler实例的处理过程中返回了 view,则需要做页面处理 if (mv != null && !mv.wasCleared()) { // todo 渲染,来解析模型和视图 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); } } 复制代码
3.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); } 复制代码
我们看一下 InternalResourceView#renderMergedOutputModel
方法:
@Override // 真真的渲染逻辑 protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. // todo 暴露model的数据作为请求域 中的数据 exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any. exposeHelpers(request); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } // If already included or response already committed, perform include, else forward. if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including [" + getUrl() + "]"); } rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to [" + getUrl() + "]"); } // todo 转发 rd.forward(request, response); } } 复制代码