SpringMVC DispatcherServlet执行流程及源码分析 逃离我推掉我的手 2022-03-15 20:58 253阅读 0赞 ## ispatcherServlet执行流程及相关源码分析 ## 在前一篇文章[SpringMVC 启动流程及相关源码分析][SpringMVC]中,详细探讨了`Spring MVC`在`Web容器`中部署后的启动过程,以及相关源码分析,同时也讨论了`DispatcherServlet类`的初始化创建过程,相关内容在此不再赘述,如有需求可查阅。 本文主要讲解`DispatcherServlet类`获取用户请求到响应的全过程,并针对相关源码进行分析。对于基本的`MVC架构`本文不再进行讲解,有需要的读者可自行查阅。 首先,让我们站在`Spring MVC`的四大组件:`DispatcherServlet`、`HandlerMapping`、`HandlerAdapter`以及`ViewResolver`的角度来看一下`Spring MVC`对用户请求的处理过程,有如下时序图: ![Image 1][] DispatcherServlet执行序列图 具体处理过程如下: * 1、用户请求发送至`DispatcherServlet类`进行处理。 * 2、`DispatcherServlet类`遍历所有配置的`HandlerMapping类`请求查找`Handler`。 * 3、`HandlerMapping类`根据`request请求`的`URL`等信息查找能够进行处理的`Handler`,以及相关拦截器`interceptor`并构造`HandlerExecutionChain`。 * 4、`HandlerMapping类`将构造的`HandlerExecutionChain类`的对象返回给前端控制器`DispatcherServlet类`。 * 5、前端控制器拿着上一步的`Handler`遍历所有配置的`HandlerAdapter类`请求执行`Handler`。 * 6、`HandlerAdapter类`执行相关`Handler`并获取`ModelAndView类`的对象。 * 7、`HandlerAdapter类`将上一步`Handler`执行结果的`ModelAndView 类`的对象返回给前端控制器。 * 8、`DispatcherServlet类`遍历所有配置的`ViewResolver类`请求进行视图解析。 * 9、`ViewResolver类`进行视图解析并获取`View`对象。 * 10、`ViewResolver类`向前端控制器返回上一步骤的`View`对象。 * 11、`DispatcherServlet类`进行视图`View`的渲染,填充`Model`。 * 12、`DispatcherServlet类`向用户返回响应。 通过时序图和上面的讲解不难发现,整个`Spring MVC`对于用户请求的响应和处理都是以`DispatcherServlet类`为核心,其他三大组件均与前端控制器进行交互,三大组件之间没有交互并且互相解耦,因此,三大组件可以替换不同的实现而互相没有任何影响,提高了整个架构的稳定性并且降低了耦合度。接下来会按照上述的响应过程逐一进行讲解。 `DispatcherServlet类`本质上依旧是一个`Servlet`并且其父类实现了`Servlet接口`,我们知道,`Servlet`执行`Service()`方法对用户请求进行响应,根据前一篇文章的分析方法可以得到人如下的调用逻辑图: ![Image 1][] service方法调用逻辑 从上图的源码调用逻辑可以看出,`HttpServlet抽象类`实现了`Servlet接口`的`service(ServletRequest, ServletResponse)`的方法,因此,用户请求的第一执行方法为该方法,该方法紧接着直接调用了`service(HttpServletRequest, HttpServletResponse)`方法,其子类`FrameworkServlet抽象类`重写了该方法,因为多态的特性最终是调用了`FrameworkServlet抽象类`的`service(HttpServletRequest, HttpServletResponse)`方法,`FrameworkServlet抽象类`同样也重写了`doHead()`、`doPost()`、`doPut()`、`doDelete()`、`doOptions()`、`doTrace()`方法,`service(ServletRequest, ServletResponse)`方法根据请求类型的不同分别调用上述方法,上述六个方法都调用了`processRequest()`方法,而该方法最终调用了`DispatcherServlet类`的`doService()`方法。通过层层分析,我们找到了最终要调用的处理用户请求的方法,`doService()`之前的方法调用都比较简单,这里不再逐一来查看源码,有兴趣的读者可以自行查阅。 查看`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 { if (logger.isDebugEnabled()) { String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : ""; logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(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<String, Object>(); 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. /* 将当前Servlet的子IoC容器放入request请求中 由此,我们可以访问到当前IoC子容器以及根IoC容器中的Bean */ 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()); 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); } } } } `doService()`方法主要进行一些参数的设置,并将部分参数放入`request`请求中,真正执行用户请求并作出响应的方法则为`doDispatch()`方法,查看`doDispatch()`方法的源码如下: /** * Process the actual dispatching to the handler. * <p>The handler will be obtained by applying the servlet's HandlerMappings in order. * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters * to find the first that supports the handler class. * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers * themselves to decide which methods are acceptable. * @param request current HTTP request * @param response current HTTP response * @throws Exception in case of any kind of processing failure */ protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { //用户的request请求 HttpServletRequest processedRequest = request; //HandlerExecutionChain局部变量 HandlerExecutionChain mappedHandler = null; //判断是否解析了文件类型的数据,如果有最终需要清理 boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { //ModelAndView局部变量 ModelAndView mv = null; //处理异常局部变量 Exception dispatchException = null; try { //检查是否包含文件等类型的数据 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. //向HandlerMapping请求查找HandlerExecutionChain mappedHandler = getHandler(processedRequest); //如果HandlerExecutionChain为null,则没有能够进行处理的Handler,抛出异常 if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. //根据查找到的Handler请求查找能够进行处理的HandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. //判断自上次请求后是否有修改,没有修改直接返回响应 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } /* 按顺序依次执行HandlerInterceptor的preHandle方法 如果任一HandlerInterceptor的preHandle方法没有通过则不继续进行处理 */ if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. //通过HandlerAdapter执行查找到的handler mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); //逆序执行HandlerInterceptor的postHandle方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } //渲染视图填充Model,如果有异常渲染异常页面 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { //如果有异常按倒序执行所有HandlerInterceptor的afterCompletion方法 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { //如果有异常按倒序执行所有HandlerInterceptor的afterCompletion方法 triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { //倒序执行所有HandlerInterceptor的afterCompletion方法 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. //如果请求包含文件类型的数据则进行相关清理工作 if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } } 根据上述源码并结合文章开始讲解的`DispatcherServlet类`结合三大组件对用户请求的处理过程不难理解相关处理流程。 `doDispatch()`方法通过调用`getHandler()`方法并传入`reuqest`通过`HandlerMapping`查找`HandlerExecutionChain`,查看其源码如下: /** * Return the HandlerExecutionChain for this request. * <p>Tries all handler mappings in order. * @param request current HTTP request * @return the HandlerExecutionChain, or {@code null} if no handler could be found */ protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping hm : this.handlerMappings) { 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; } `getHandler()`方法遍历了开发者配置的所有`HandlerMapping类`根据`request`请求来查找`HandlerExecutionChain`,从这里可以看出,`Spring MVC`是支持用户配置多个`HandlerMapping类`的,在处理用户请求时会逐一查找,找到后立即返回,因此,如果多个`HandlerMapping类`都能够处理同一`request`请求,只会返回第一个能够处理的`HandlerMapping类`构造的`HandlerExecutionChain`,所以在配置`HandlerMapping类`时需要注意不要对同一请求多次进行处理,由于篇幅问题`HandlerMapping类`如何具体查找`Handler`并构造`HandlerExecutionChain`的细节不在此进行讲解,如有兴趣可以查阅本系列文章的第三篇[SpringMVC HandlerMapping源码分析][SpringMVC HandlerMapping]。 如果没有找到对应的`HandlerExecutionChain`对象,则会执行`noHandlerFound()`方法,继续查看其源码如下: /** * No handler found -> set appropriate HTTP response status. * @param request current HTTP request * @param response current HTTP response * @throws Exception if preparing the response failed */ protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception { if (pageNotFoundLogger.isWarnEnabled()) { pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) + "] in DispatcherServlet with name '" + getServletName() + "'"); } if (this.throwExceptionIfNoHandlerFound) { throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request), new ServletServerHttpRequest(request).getHeaders()); } else { response.sendError(HttpServletResponse.SC_NOT_FOUND); } } 如果没有找到对应的`HandlerExecutionChain`则会抛出异常`NoHandlerFoundException`,在开发的过程中,如果我们将具体的`URL`写错了则会遇到这个404错误。 继续查看`doDispatch()`方法的源码,如果找到了`HandlerExecutionChain`接下来会调用`getHandlerAdapter()`方法来查找能够对`Handler`进行处理的`HandlerAdapter`,查看其源码如下: /** * Return the HandlerAdapter for this handler object. * @param handler the handler object to find an adapter for * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error. */ protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { for (HandlerAdapter ha : this.handlerAdapters) { if (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"); } 与`HandlerMapping`类似,查找能够处理具体`Handler`的`HandlerAdapter`时同样会遍历所有配置了的`HandlerAdapter`,`HandlerAdapter`是一个接口包含一个`support()`方法,该方法根据`Handler`是否实现某个特定的接口来判断该`HandlerAdapter`是否能够处理这个具体的`Handler`,这里使用适配器模式,通过这样的方式就可以支持不同类型的`HandlerAdapter`。如果没有查找到能够处理`Handler`的`HandlerAdapter`则会抛出异常,如果在开发的过程中`Handler`在实现接口时出现了问题就可能会遇到上述异常。 查找到了对应的`HandlerAdapter`后就会调用`HandlerExecutionChain`的`applyPreHandle()`方法来执行配置的所有`HandlerInteceptor`的`preHandle()`方法,查看其源码如下: /** * Apply preHandle methods of registered interceptors. * @return {@code true} if the execution chain should proceed with the * next interceptor or the handler itself. Else, DispatcherServlet assumes * that this interceptor has already dealt with the response itself. */ boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } } return true; } `HandlerExecutionChain`的`applyPreHandle()`方法会按照顺序依次调用`HandlerInterceptor`的`preHandle()`方法,但当任一`HandlerInterceptor`的`preHandle()`方法返回了`false`就不再继续执行其他`HandlerInterceptor`的`preHandle()`方法,而是直接跳转执行`triggerAfterCompletion()`方法,查看该方法源码如下: /** * Trigger afterCompletion callbacks on the mapped HandlerInterceptors. * Will just invoke afterCompletion for all interceptors whose preHandle invocation * has successfully completed and returned true. */ void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, 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); } } } } 这里遍历的下标为`interceptorIndex`,该变量在前一个方法`applyPreHandle()`方法中赋值,如果`preHandle()`方法返回`true`该变量加一,因此该方法会逆序执行所有`preHandle()`方法返回了`true`的`HandlerInterceptor`的`afterCompletion()`方法。到这里读者已经掌握了`HandlerInterceptor`的`preHandle()`方法以及`afterCompletion()`方法的执行顺序,这些内容并不需要我们死记,需要知道其执行顺序查看源码是最好的方法。 继续阅读`doDispatch()`方法的源码,如果所有拦截器的`preHandle()`方法都返回了`true`没有进行拦截,接下来前端控制器会请求执行上文获取的`Handler`,这个`Handler`就是开发的时候编写的`Controller`,根据实现接口的不同执行相关方法,并获取到`ModelAndView类`的对象。 接下来会执行`HandlerInterceptor`的`postHandle()`方法,具体源码如下: /** * Apply postHandle methods of registered interceptors. */ void applyPostHandle(HttpServletRequest request, HttpServletResponse response, 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); } } } 可以发现,`postHandle()`方法是按照逆序执行。 执行完`postHandle()`方法后,`doDispatch()`方法调用了`processDispatchResult()`方法,其源码如下: /** * Handle the result of handler selection and handler invocation, which is * either a ModelAndView or an Exception to be resolved to a ModelAndView. */ private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; //判断HandlerMapping、HandlerAdapter处理时的异常是否为空 if (exception != null) { //上述两个组件处理时的异常不为空 //如果为ModelAndViewDefiningException异常,则获取一个异常视图 if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } //如果不为ModelAndViewDefiningException异常,进行异常视图的获取 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? //判断mv是否为空,不管是正常的ModelAndView还是异常的ModelAndView,只要存在mv就进行视图渲染 if (mv != null && !mv.wasCleared()) { 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()) { // Concurrent handling started during a forward return; } //执行相关HandlerInterceptor的afterCompletion()方法 if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } } 该方法传入了一个异常类的对象`dispatchException`,阅读`doDispatch()`方法的源码可以看出,`Spring MVC`对整个`doDispatch()`方法用了嵌套的`try-catch`语句,内层的`try-catch`用于捕获`HandlerMapping`进行映射查找`HandlerExecutionChain`以及`HandlerAdapter`执行具体`Handler`时的处理异常,并将异常传入到上述`processDispatchResult()`方法中。 `processDispatchResult()`方法主要用于针对产生的异常来构造异常视图,接着不管视图是正常视图还是异常视图均调用`render()`方法来渲染,查看`render()`方法的具体源码如下: /** * Render the given ModelAndView. * <p>This is the last stage in handling a request. It may involve resolving the view by name. * @param mv the ModelAndView to render * @param request current HTTP servlet request * @param response current HTTP servlet response * @throws ServletException if view is missing or cannot be resolved * @throws Exception if there's a problem rendering the view */ protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. Locale locale = this.localeResolver.resolveLocale(request); response.setLocale(locale); View view; if (mv.isReference()) { // We need to resolve the view name. // 解析视图名称获取对应视图View view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); //如果视图View为空抛出异常 if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } // Delegate to the View object for rendering. if (logger.isDebugEnabled()) { logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); } try { //设置Http响应状态字 if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } //调用视图View的render方法通过Model来渲染视图 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; } } `render()`方法通过调用`resolveViewName()`方法根据视图名称解析对应的视图`View`,该方法源码如下: /** * Resolve the given view name into a View object (to be rendered). * <p>The default implementations asks all ViewResolvers of this dispatcher. * Can be overridden for custom resolution strategies, potentially based on * specific model attributes or request parameters. * @param viewName the name of the view to resolve * @param model the model to be passed to the view * @param locale the current locale * @param request current HTTP servlet request * @return the View object, or {@code null} if none found * @throws Exception if the view cannot be resolved * (typically in case of problems creating an actual View object) * @see ViewResolver#resolveViewName */ protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } return null; } `resolveViewName()`方法通过遍历配置的所有`ViewResolver类`根据视图名称来解析对应的视图`View`,如果找到则返回对应视图`View`,没有找到则返回`null`。 回到前一个`render()`方法,如果上述方法返回的视图为`null`则抛出异常,这个异常相信大多数人也见过,当开发时写错了返回的`View`视图名称时就会抛出该异常。接下来调用具体视图的`render()`方法来进行`Model`数据的渲染填充,最终构造成完整的视图。 到这里,`doDispatch()`的外层`try-catch`异常的作用我们就知道了,为了捕获渲染视图时的异常,通过两层嵌套的`try-catch`,`Spring MVC`就能够捕获到三大组件在处理用户请求时的异常,通过这样的方法能够很方便的实现统一的异常处理。 ## 总结 ## 通过前文的源码分析,我们能够清楚的认识到`Spring MVC`对用户请求的处理过程,进一步加深对`Spring MVC`的理解。 转载自:https://www.jianshu.com/p/0f981efdfbbd [SpringMVC]: https://www.jianshu.com/p/dc64d02e49ac [Image 1]: [SpringMVC HandlerMapping]: https://www.jianshu.com
还没有评论,来说两句吧...