上一篇我们分析了 web 环境下容器的初始化过程,探究了在 web 项目启动过程中,Spring MVC 所执行的一系列初始化工作,本篇中我们将一起来跟踪一次 Spring MVC 对于请求的具体处理过程,从整体上对 Spring MVC 的逻辑处理进行感知,先把握整体,后追究细节。
我们定义的控制器方法很简单,接收请求参数,然后记录到 Model 中并回显到页面上,实现如下:
1 2 3 4 5 6 7 8 9 @RequestMapping("/hello") public ModelAndView hello (@RequestParam("name") String name) { log.info("Do demo hello, name={}" , name); ModelAndView mav = new ModelAndView(); mav.setViewName("hello" ); mav.addObject("name" , name); return mav; }
当我们在浏览器中输入请求地址 http://localhost:8080/demo/hello?name=zhenchao
的时候,在请求到达服务器之前会经历域名解析、TCP连接、数据发送等过程,而这些都不是本文所要关注的重点,我们所要关注的是请求到达了 servlet 容器,执行一系列操作之后准备向客户端发送响应这中间的过程,排除服务器软件解析和封装的操作。
上面的时序图展示了本次请求处理的完整过程,接下来我们从源码层面对整个过程进行分析。当该请求到达服务器之后,服务器首先会为该请求创建一个 socket 连接,然后创建对应的 request 和 response 对象封装请求信息,并交由相应的 servlet 进行处理。请求首先会进入 HttpServlet 的 service(ServletRequest req, ServletResponse res)
方法,该方法会将 ServletRequest 和 ServletResponse 分别转换成 HttpServletRequest 和 HttpServletResponse 对象,然后调用 service(HttpServletRequest request, HttpServletResponse response)
方法进行处理,FrameworkServlet 覆盖实现了该方法:
1 2 3 4 5 6 7 8 9 protected void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); if (HttpMethod.PATCH == httpMethod || httpMethod == null ) { this .processRequest(request, response); } else { super .service(request, response); } }
该方法主要功能是判定当前请求的方法类型,如果是 PATCH 方法或未知的方法类型则调用 processRequest 进行处理,否则直接委托父类的 service(HttpServletRequest request, HttpServletResponse response)
方法。而我们这里是以 GET 方法请求,所以直接进入 else 分支,即进入 HttpServlet 的 service 方法,该方法也是一个决策方法,用于判断当前的请求类型,并调用相应的 do 方法,这里我们会进入 doGet 方法逻辑,位于 FrameworkServlet 中的覆盖:
1 2 3 4 @Override protected final void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .processRequest(request, response); }
这里只是简单的调用了 processRequest 方法,前面在判定为 PATCH 请求方法类型时调用的也是此方法,而 GET 方法这样绕一下也是为了对请求目标对象的变更时间进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 protected final void processRequest (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null ; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = this .buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = this .buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); this .initContextHolders(request, localeContext, requestAttributes); try { this .doService(request, response); } catch (ServletException ex) { failureCause = ex; throw ex; } catch (IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed" , ex); } finally { this .resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null ) { requestAttributes.requestCompleted(); } this .publishRequestHandledEvent(request, response, startTime, failureCause); } }
processRequest 首先获取并记录之前的 LocaleContext 和 RequestAttributes 对象,然后基于当前的请求构造新的 LocaleContext 和 RequestAttributes 对象并调用 initContextHolders 方法将其记录到相应的 holder 中,然后调用核心方法 doService 继续处理请求,等到该方法返回时会利用之前记录的 LocaleContext 和 RequestAttributes 对象更新对应的 holder。接下来我们继续探究 doService 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 protected void doService (HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "" ; logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]" ); } Map<String, Object> attributesSnapshot = null ; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<String, Object>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this .cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet" )) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this .getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this .localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this .themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, this .getThemeSource()); 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 { this .doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { if (attributesSnapshot != null ) { this .restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
该方法主要还是为调用 doDispatch 方法做一些准备工作,记录了一些信息到 attribute 中。这里对 FlashMap 做一下说明,FlashMap 的主要功能是简化 redirect 请求传参,我们都知道记录在 request 中的参数在 redirect 时无法带到目标方法,基于纯 JSP 开发过 web 服务的同学一定曾经为此机制感到苦恼过,而解决这种问题最朴素的方法就是将参数拼接在请求地址的后面,但是这样的方式除了有长度限制之外,有时候还会有一定的安全问题。Spring MVC 则提供了 FlashMap 组件以解决这一问题,我们可以通过将需要 redirect 时传递的参数记录到 FlashMap 中即可实现传递,框架会自动将这些参数记录到目标接口的 Model 中,而这一机制本质上是基于 session 实现的。下面来举个例子帮大家回忆一下 FlashMap 的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RequestMapping("/redirect") public String redirectParams (HttpServletRequest request, RedirectAttributes ra) { ra.addFlashAttribute("user_id" , RandomUtils.nextLong()); ra.addAttribute("username" , "zhenchao" ); FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request); flashMap.put("order_id" , RandomUtils.nextLong()); flashMap = (FlashMap) request.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE); flashMap.put("imei" , RandomStringUtils.randomAlphanumeric(32 )); return "redirect:display-result" ; }
我们可以通过 RedirectAttributes 记录需要 redirect 传递的参数,RedirectAttributes 提供了两种方式,addFlashAttribute 方法会将参数记录到 FlashMap 对象中进行传递,而 addAttribute 方法则会将参数拼接在请求地址后面进行传递;此外我们还可以通过 RequestContextUtils 获取 FlashMap 对象直接往里面注入值,而这一方法本质上与上面列子中的第三种方法是相同的,框架最终还是以 DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE
作为 key 将 FlashMap 对象记录在 attribute 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 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 = this .checkMultipart(request); multipartRequestParsed = (processedRequest != request); mappedHandler = this .getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null ) { this .noHandlerFound(processedRequest, response); return ; } HandlerAdapter adapter = this .getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET" .equals(method); if (isGet || "HEAD" .equals(method)) { long lastModified = adapter.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return ; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return ; } mv = adapter.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return ; } this .applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed" , err); } this .processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { this .triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { this .triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed" , err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null ) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { this .cleanupMultipart(processedRequest); } } } }
doDispatch 在实现上定义了整个处理的执行流程,而具体的执行逻辑都交由相应的类来实现,可以概括如下:
检测当前是否是文件上传的请求,如果是的话则调用 MultipartResolver 的 resolveMultipart 方法解析 request 封装为 MultipartHttpServletRequest 对象
调用 HandlerMapping 获取当前请求对应的处理器
获取当前处理器对应的 HandlerAdapter
对于 GET 或 HEAD 方法检查目标页面是否有变更,没有的话直接返回
应用所有拦截器的 prehandle() 方法
激活处理器进行逻辑处理并返回视图,这一步最终会调用开发者自定义的实现
检测上一步如果没有设置视图,则使用默认视图
应用所有拦截器的 postHandle() 方法
处理返回结果,包括异常处理、页面渲染,以及执行拦截器的 afterCompletion() 方法
针对文件上传请求,执行临时数据的清理
整个处理过程的背后是 Spring MVC 九大基础核心类的支撑,在此我们先不展开,后面会用专门的篇章逐一分析,我们先简单介绍一下过程中涉及到的一些概念,以对整个过程进行整体上的感知。
上述过程涉及到三个核心组件的分工合作:Handler、HandlerMapping,以及 HandlerAdapter。方法中首先通过 HandlerMapping 基于当前请求找到对应的 Handler,然后基于 Handler 找到对应的 HandlerAdapter,最后通过 HandlerAdapter 激活 Handler 来执行业务逻辑并返回响应视图。
Handler 可以理解为处理器,简单来说它是对附加了拦截器的控制器方法的封装;而 HandlerMapping 则是用来建立请求与对应 Handler 之间的映射关系,因为一个请求最终还是要落到一个控制器方法上去处理,而 HandlerMapping 则负责中间的过程映射;最后再来看看 HandlerAdapter ,有了 Handler 和 HandlerMapping,我们可以找到一个请求对应的处理方法,并对该请求进行处理和响应,似乎已经完美了,那么我们还缺什么呢?这里还是需要记住一点,Spring MVC 是基于 servlet 的 MVC 框架,不像 Play Framework 、Vert.x-Web 等 web 框架另起炉灶的解决方案,所以我们的请求最终还是要交由 servlet 去处理。那么我们为什么要引入 MVC 框架,而不直接基于 servlet 来进行 web 开发呢?简单点来说就是框架比原生的 servlet 好用,我们可以灵活的实现我们的控制器方法,而不需要受 servlet 方法约定的束缚,而这灵活的背后就是 HandlerAdapter 的功劳,HandlerAdapter 的作用从根本上来说就是建立 Handler 与原生 servlet 方法间的适配,所以它是一个适配器。
接下来我们对上述过程的几个核心步骤展开继续分析,首先看一下通过 HandlerMapping 获取 Handler 的过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected HandlerExecutionChain getHandler (HttpServletRequest request) throws Exception { for (HandlerMapping hm : this .handlerMappings) { if (logger.isTraceEnabled()) { logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'" ); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null ) { return handler; } } return null ; }
该方法通过遍历已注册的 HandlerMapping 来获取当前请求对应的 HandlerExecutionChain(封装了具体的 Handler 和对应的拦截器),核心还是在于调用了 HandlerMapping 的 getHandler()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public final HandlerExecutionChain getHandler (HttpServletRequest request) throws Exception { Object handler = this .getHandlerInternal(request); if (handler == null ) { handler = this .getDefaultHandler(); } if (handler == null ) { return null ; } if (handler instanceof String) { String handlerName = (String) handler; handler = this .getApplicationContext().getBean(handlerName); } HandlerExecutionChain executionChain = this .getHandlerExecutionChain(handler, request); if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this .corsConfigSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = this .getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = this .getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
方法首先调用了 getHandlerInternal 方法以解析当前请求对应的 Handler,如果解析不到则获取默认的 Handler。如果当前的 Handler 还仅仅是一个 beanName,则会从容器中去获取对应的实例,然后利用 HandlerExecutionChain 对当前 Handler 实例进行包装,并附加定义的拦截器。最后会检测并处理 CORS(Cross-origin resource sharing) 请求。这里最核心的当属 getHandlerInternal 方法,该方法尝试获取当前请求所映射的自定义控制器方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 protected HandlerMethod getHandlerInternal (HttpServletRequest request) throws Exception { String lookupPath = this .getUrlPathHelper().getLookupPathForRequest(request); if (logger.isDebugEnabled()) { logger.debug("Looking up handler method for path " + lookupPath); } this .mappingRegistry.acquireReadLock(); try { HandlerMethod handlerMethod = this .lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null ); } finally { this .mappingRegistry.releaseReadLock(); } }
方法的逻辑还是很简洁的,针对本次的请求在第一步时获取到有效的请求路径 /demo/hello
,然后调用 lookupHandlerMethod(String lookupPath, HttpServletRequest request)
方法基于请求路径获取对应的处理器方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 ) { this .addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { this .addMatchingMappings(this .mappingRegistry.getMappings().keySet(), matches, request); } if (!matches.isEmpty()) { Comparator<Match> comparator = new MatchComparator(this .getMappingComparator(request)); Collections.sort(matches, comparator); if (logger.isTraceEnabled()) { logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches); } Match bestMatch = matches.get(0 ); if (matches.size() > 1 ) { if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; } Match secondBestMatch = matches.get(1 ); if (comparator.compare(bestMatch, secondBestMatch) == 0 ) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + m1 + ", " + m2 + "}" ); } } this .handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } else { return this .handleNoMatch(this .mappingRegistry.getMappings().keySet(), lookupPath, request); } }
lookupHandlerMethod 的主要逻辑是基于当前的请求路径从 mappingRegistry 集合(在 AbstractHandlerMethodMapping 中基于 InitializingBean 机制执行初始化)中获取对应的请求匹配信息 RequestMappingInfo 集合,然后遍历集合从 mappingRegistry 中拿到 RequestMappingInfo 对象所匹配的 HandlerMethod ,并由 Match 对象一起封装记录到匹配信息集合中,最后对集合进行排序并选择排在第一个的 HandlerMethod 作为最佳匹配,同时校验是否存在多个最佳匹配,如果存在则抛 IllegalStateException 异常。
到这里通过 HandlerMapping 获取对应 Handler 的逻辑已经走完,方法返回了创建的 HandlerExecutionChain 对象,接下来我们继续回到 doDispatch 方法中,基于刚刚获取到的 Handler 调用 getHandlerAdapter 方法获取对应的 HandlerAdapter:
1 2 3 4 5 6 7 8 9 10 11 12 13 protected HandlerAdapter getHandlerAdapter (Object handler) throws ServletException { for (HandlerAdapter ha : this .handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]" ); } if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler" ); }
1 2 3 4 public final boolean supports (Object handler) { return (handler instanceof HandlerMethod && this .supportsInternal((HandlerMethod) handler)); }
适配器的获取过程相当简单,如上述注释。接下来继续来看 HandlerAdapter 执行处理器具体逻辑的过程:
1 2 3 4 public final ModelAndView handle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return this .handleInternal(request, response, (HandlerMethod) handler); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 protected ModelAndView handleInternal (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; this .checkRequest(request); if (this .synchronizeOnSession) { HttpSession session = request.getSession(false ); if (session != null ) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = this .invokeHandlerMethod(request, response, handlerMethod); } } else { mav = this .invokeHandlerMethod(request, response, handlerMethod); } } else { mav = this .invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (this .getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { this .applyCacheSeconds(response, this .cacheSecondsForSessionAttributeHandlers); } else { this .prepareResponse(response); } } return mav; }
上述方法的逻辑已经注释的比较清晰,首先检查当前的请求方法是否被支持,以及是否需要为当前请求创建 session,然后激活处理器方法,最后为响应头添加 Cache-Control
逻辑,来进一步探究 invokeHandlerMethod 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 protected ModelAndView invokeHandlerMethod (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { WebDataBinderFactory binderFactory = this .getDataBinderFactory(handlerMethod); ModelFactory modelFactory = this .getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = this .createInvocableHandlerMethod(handlerMethod); invocableMethod.setHandlerMethodArgumentResolvers(this .argumentResolvers); invocableMethod.setHandlerMethodReturnValueHandlers(this .returnValueHandlers); invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this .parameterNameDiscoverer); 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(); if (logger.isDebugEnabled()) { logger.debug("Found concurrent result value [" + result + "]" ); } invocableMethod = invocableMethod.wrapConcurrentResult(result); } invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null ; } return this .getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
该方法所做的工作主要可以概括为三个步骤:1.构造并初始化处理器方法的执行条件;2.激活调用处理方法;3.构造 ModelAndView 对象并返回。我们来逐步探究各个过程,在第一步中我们主要来深入一下 Model 的初始化过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public void initModel (NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception { Map<String, ?> sessionAttributes = this .sessionAttributesHandler.retrieveAttributes(request); container.mergeAttributes(sessionAttributes); this .invokeModelAttributeMethods(request, container); for (String name : this .findSessionAttributeArguments(handlerMethod)) { if (!container.containsAttribute(name)) { Object value = this .sessionAttributesHandler.retrieveAttribute(request, name); if (value == null ) { throw new HttpSessionRequiredException("Expected session attribute '" + name + "'" , name); } container.addAttribute(name, value); } } }
在整个初始化过程中,方法先取出 SessionAttributes 中保存的参数合并到 ModelAndViewContainer 对象中;然后执行 @ModelAttribute
注解的方法,这一步的最终目的是将方法返回值设置到 Model 中;最后遍历被 @ModelAttribute
和 @SessionAttribute
同时注解的属性,这里的目的还是将对应的属性值记录到 Model 中,区别在于这里的属性来源是其它控制器方法记录到 session 中的值。
接下来我们分析一下处理器方法的激活调用过程,该方法主要做了两件事情:1.激活处理器方法并拿到方法返回值;2.调用注册的 HandlerMethodReturnValueHandler 对返回值进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public void invokeAndHandle (ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = this .invokeForRequest(webRequest, mavContainer, providedArgs); this .setResponseStatus(webRequest); if (returnValue == null ) { if (this .isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true ); return ; } } else if (StringUtils.hasText(this .responseReason)) { mavContainer.setRequestHandled(true ); return ; } mavContainer.setRequestHandled(false ); try { this .returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getReturnValueHandlingErrorMessage("Error handling return value" , returnValue), ex); } throw ex; } }
到这里我们已经离我们自定义的控制器方法已经很近了,接下去的逻辑就是拿到我们自定义控制器方法的参数值,并基于反射执行该方法,这一过程位于 invokeForRequest 中,逻辑比较简单,这里不再展开。如果把整个跟踪的过程比作是一次旅行,那么到这里我们基本上算是到达了旅行的目的地,而接下去将开始返程了,我们首先回到 RequestMappingHandlerAdapte 的 invokeHandlerMethod 中,在执行完上面的 invokeAndHandle 后,我们继续往下执行 ModelAndView 对象的获取过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private ModelAndView getModelAndView (ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { 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); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; }
该过程首先更新 Model,然后创建 ModelAndView 对象并设置视图,如果是 redirect 返回还会处理我们添加到 FlashMap 中的跳转参数,整个过程唯一间接实现的逻辑是更新 Model:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void updateModel (NativeWebRequest request, ModelAndViewContainer container) throws Exception { ModelMap defaultModel = container.getDefaultModel(); if (container.getSessionStatus().isComplete()) { this .sessionAttributesHandler.cleanupAttributes(request); } else { this .sessionAttributesHandler.storeAttributes(request, defaultModel); } if (!container.isRequestHandled() && container.getModel() == defaultModel) { this .updateBindingResult(request, defaultModel); } }
updateModel 是 ModelFactory 中主要包含的两个方法之一(另外一个是 initModel,已在前面解读过),该方法的功能如注释所示,主要做了两件事情:
对于 SessionAttributes 进行处理,如果在控制器中调用了 SessionStatus.setComplete()
方法,那么这里会执行对 SessionAttributes 的清空,否则将 Model 中相应的属性记录到 SessionAttributes 中。
判断是否需要执行页面渲染,若需要则为相应的属性添加 BindingResult 对象。
getModelAndView 方法执行完成之后,接下来我们回到 doDispatch 方法继续后续的处理过程。如果处理器方法未设置视图,则接下来会为本次请求设置一个默认的视图,然后调用所有拦截器的 postHandle()
方法,接着开始执行页面的渲染逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 private void processDispatchResult ( HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, 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 = this .processHandlerException(request, response, handler, exception); errorView = (mv != null ); } } if (mv != null && !mv.wasCleared()) { this .render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isDebugEnabled()) { logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request handling" ); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return ; } if (mappedHandler != null ) { mappedHandler.triggerAfterCompletion(request, response, null ); } }
上述方法首先会判断前面的处理过程中是否出现异常,如果有异常则需要对异常信息进行处理,需要注意的一点是这里的处理逻辑位于页面渲染之前,如果渲染过程中出现了异常则不会被处理。具体的异常处理过程不再展开,留到以后专门讲解,下面来看一下页面的渲染过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 protected void render (ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { Locale locale = this .localeResolver.resolveLocale(request); response.setLocale(locale); View view; if (mv.isReference()) { view = this .resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); if (view == null ) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'" ); } } else { 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() + "'" ); } } if (logger.isDebugEnabled()) { logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'" ); } 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 + "] in DispatcherServlet with name '" + getServletName() + "'" , ex); } throw ex; } }
整个方法分为三步来执行,如注释所示,其中第一、二步比较基础,而第三步的页面渲染过程则委托给视图对象来执行:
1 2 3 4 5 6 7 8 9 10 11 public void render (Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isTraceEnabled()) { logger.trace("Rendering view with name '" + this .beanName + "' with model " + model + " and static attributes " + this .staticAttributes); } Map<String, Object> mergedModel = this .createMergedOutputModel(model, request, response); this .prepareResponse(request, response); this .renderMergedOutputModel(mergedModel, this .getRequestToExpose(request), response); }
渲染的过程首先就是解析请求过程的动态和静态属性值,并封装到一个 map 中以便后续取值;然后执行一些准备工作,默认的实现是为了修复 IE 浏览器中响应 HTTPS 下载请求的 bug ;最后执行渲染逻辑,这一块留到后续分析视图实现时进行针对性的说明。
继续回到 doDispatch,拦截器 HandlerInterceptor 中声明了三个方法:preHandle、postHandle,以及 afterCompletion。前面的执行过程围绕处理器方法分别在前后执行了 preHandle 和 postHandle,而 afterCompletion 也在页面渲染过程完成之后被触发。如果本次是上传请求,那么接下来会执行清理上传过程中产生的临时文件的过程,到这里就完成了 doDispatch 整个方法过程的执行。
回到开始位于 FrameworkServlet 中的 processRequest 方法,记得一开始我们对之前的 LocaleContext 和 RequestAttributes 进行了暂存,而此时则需要将相应的 holder 利用这些暂存的值恢复到最开始的状态,从而不影响其它请求的执行,并将 requestActive 置为 false,标记本次请求的完成,最后调用 publishRequestHandledEvent 方法,发布请求处理完成的通知消息。
到这里,这一次简单的请求就处理完成了,我们可以在浏览器中看到具体渲染后的页面。虽然是一个简单的请求,但是通过分析我们也看到框架背后复杂的处理逻辑,这还仅仅是整个处理过程的主线流程,后续我们会针对各个核心支撑组件逐一展开来进行针对性的分析,本篇文章的目的主要还是对整个请求有一个全局的感知,后续我们在具体分析各个组件时时可以具体定位到具体是发生在哪一步的请求,从而能够联系上下文进行理解。