创意电子

标题: 精尽Spring MVC源码分析 - 一个请求的旅行过程 [打印本页]

作者: Java码农之路    时间: 2021-4-23 11:26
标题: 精尽Spring MVC源码分析 - 一个请求的旅行过程
我们先来了解一个哀求是怎样被 Spring MVC 处置惩罚的,由于整个流程涉及到的代码非常多,所以本文的重点在于剖析整体的流程,主要讲解 DispatcherServlet 这个核心类,弄懂了这个流程后,才气更好的理解具体的源码,回过头再来看则会更加的豁然开朗

整体流程图


                               
登录/注册后可看大图

Spring MVC 处置惩罚哀求的流程大抵如上图所示

以上就是 Spring MVC 处置惩罚哀求的全过程,上面的流程进行了一定的简化,主要涉及到最核心的组件,还有许多其他组件没有表现出来,不外这并不影响大家对主过程的理解。

组件预览

在上一篇《WebApplicationContext 容器的初始化》文档报告 FramworkServlet 的 onRefresh 方法时,该方法由 DispatcherServlet 去实现,会初始化九大组件,怎样初始化的这里暂时不睁开讨论,默认会从 spring-webmvc 下面的 DispatcherServlet.properties 文件中读取组件的实现类,感兴趣可以先阅读一下源码,后续会依次描述

那么接下来就简单介绍一下 DispatcherServlet 和九大组件:

组件
说明
DispatcherServlet
Spring MVC 的核心组件,是哀求的入口,负责协调各个组件工作
MultipartResolver
内容范例( Content-Type )为 multipart/* 的哀求的剖析器,例如剖析处置惩罚文件上传的哀求,便于获取参数信息以及上传的文件
HandlerMapping
哀求的处置惩罚器匹配器,负责为哀求找到合适的 HandlerExecutionChain 处置惩罚器执行链,包含处置惩罚器(handler)和拦截器们(interceptors
HandlerAdapter
处置惩罚器的适配器。因为处置惩罚器 handler 的范例是 Object 范例,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处置惩罚器的实现多变,好比用户处置惩罚器可以实现 Controller 接口、HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处置惩罚器等,这就导致 Spring MVC 无法直接执行这个处置惩罚器。所以这里需要一个处置惩罚器适配器,由它去执行处置惩罚器
HandlerExceptionResolver
处置惩罚器非常剖析器,将处置惩罚器( handler )执行时发生的非常,剖析( 转换 )成对应的 ModelAndView 效果
RequestToViewNameTranslator
视图名称转换器,用于剖析出哀求的默认视图名
LocaleResolver
本地化(国际化)剖析器,提供国际化支持
ThemeResolver
主题剖析器,提供可设置应用整体样式风格的支持
ViewResolver
视图剖析器,根据视图名和国际化,得到最终的视图 View 对象
FlashMapManager
FlashMap 管理器,负责重定向时,保存参数至临时存储(默认 Session)
Spring MVC 对各个组件的职责划分得比较清楚。DispatcherServlet 负责协调,其他组件则各自做分内之事,互不干扰。经过如许的职责划分,代码会便于维护。同时对于源码阅读者来说,也会很友好。可以降低理解源码的难度,使大家可以或许快速理清主逻辑。这一点值得我们学习。

ThemeResolver 和 FlashMapManager 组件在该系列文档中不会进行讲解,因为几乎接触不到,感兴趣的可以去 Google 一下,嘻嘻~ 笔者没接触过

FrameworkServlet

固然在上面的整体流程图中,我们看到哀求是直接被 DispatcherServlet 所处置惩罚,但是现实上,FrameworkServlet 才是真正的入口,再来回顾一个 DispatcherServlet 的类图,如下:


                               
登录/注册后可看大图

FrameworkServlet 覆盖了 HttpServlet 的以下方法:

这些方法分别处置惩罚不同 HTTP 哀求范例的哀求,最终都会调用另一个 processRequest(HttpServletRequest request, HttpServletResponse response) 方法

此中 doGetdoPostdoPutdoDelete 四个方法是直接调用 processRequest 方法的

service

service(HttpServletRequest request, HttpServletResponse response) 方法,用于处置惩罚哀求,方法如下:

@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)        throws ServletException, IOException {    //  得到哀求方法    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());    //  处置惩罚 PATCH 哀求    if (httpMethod == HttpMethod.PATCH || httpMethod == null) {        processRequest(request, response);    }    //  处置惩罚其他范例的哀求    else {        super.service(request, response);    }}doOptions

doOptions(HttpServletRequest request, HttpServletResponse response)方法,用于处置惩罚 OPTIONS 范例的哀求,方法如下:

@Overrideprotected void doOptions(HttpServletRequest request, HttpServletResponse response)        throws ServletException, IOException {    // 如果 dispatchOptionsRequest 为 true ,则处置惩罚该哀求,默认为 true    if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {        // 处置惩罚哀求        processRequest(request, response);        // 如果响应 Header 包含 "Allow" ,则不需要交给父方法处置惩罚        if (response.containsHeader("Allow")) {            // Proper OPTIONS response coming from a handler - we're done.            return;        }    }    // Use response wrapper in order to always add PATCH to the allowed methods    // 调用父方法,并在响应 Header 的 "Allow" 增加 PATCH 的值    super.doOptions(request, new HttpServletResponseWrapper(response) {        @Override        public void setHeader(String name, String value) {            if ("Allow".equals(name)) {                value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();            }            super.setHeader(name, value);        }    });}
利用场景:AJAX 进行跨域哀求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS哀求头,用以判断现实发送的哀求是否安全

doTrace

doTrace(HttpServletRequest request, HttpServletResponse response)方法,用于处置惩罚 TRACE 范例的哀求,方法如下:

protected void doTrace(HttpServletRequest request, HttpServletResponse response)        throws ServletException, IOException {    // 如果 dispatchTraceRequest 为 true ,则处置惩罚该哀求,默认为 false    if (this.dispatchTraceRequest) {        // 处置惩罚哀求        processRequest(request, response);        // 如果响应的内容范例为 "message/http" ,则不需要交给父方法处置惩罚        if ("message/http".equals(response.getContentType())) {            // Proper TRACE response coming from a handler - we're done.            return;        }    }    // 调用父方法    super.doTrace(request, response);}
回显服务器收到的哀求,主要用于测试或诊断,笔者目前还没接触过

processRequest

processRequest(HttpServletRequest request, HttpServletResponse response) 方法,用于处置惩罚哀求,方法如下:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)        throws ServletException, IOException {    //  记录当前时间,用于盘算处置惩罚哀求花费的时间    long startTime = System.currentTimeMillis();    //  记录非常,用于保存处置惩罚哀求过程中发送的非常    Throwable failureCause = null;    //  TODO    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();    LocaleContext localeContext = buildLocaleContext(request);    //  TODO    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);    //  TODO    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());    //  TODO    initContextHolders(request, localeContext, requestAttributes);    try {        //  执行真正的逻辑        doService(request, response);    }    catch (ServletException | IOException ex) {        failureCause = ex; //  记录抛出的非常        throw ex;    }    catch (Throwable ex) {        failureCause = ex; //  记录抛出的非常        throw new NestedServletException("Request processing failed", ex);    }    finally {        //  TODO        resetContextHolders(request, previousLocaleContext, previousAttributes);        //  TODO        if (requestAttributes != null) {            requestAttributes.requestCompleted();        }        //  如果日志级别为 DEBUG,则打印哀求日志        logResult(request, response, failureCause, asyncManager);        //  发布 ServletRequestHandledEvent 哀求处置惩罚完成事件        publishRequestHandledEvent(request, response, startTime, failureCause);    }}
记录当前时间,用于盘算处置惩罚哀求花费的时间

记录非常,用于保存处置惩罚哀求过程中发送的非常

【核心】调用 doService(HttpServletRequest request, HttpServletResponse response) 抽象方法,执行真正的逻辑,由 DispatcherServlet 实现,所以这就是 DispatcherServlet 处置惩罚哀求的真正入口

记录执行过程抛出的非常,最终在 finally 的代码段中利用。

如果日志级别为 DEBUG,则打印哀求日志

调用 publishRequestHandledEvent 方法,通过 WebApplicationContext 发布 ServletRequestHandledEvent 哀求处置惩罚完成事件,目前好像 Spring MVC 没有监听这个事件,可以本身写一个监听器用于获取哀求信息,示例如下:

@Component@Log4j2public class ServletRequestHandledEventListener implements ApplicationListener{        @Override        public void onApplicationEvent(ServletRequestHandledEvent event) {                log.info("哀求描述:{}", event.getDescription());                log.info("哀求路径:{}", event.getRequestUrl());        log.info("开始时间:{}", event.getTimestamp());                log.info("哀求耗时:{}", event.getProcessingTimeMillis());                log.info("状 态 码:{}", event.getStatusCode());        log.info("失败缘故原由:{}", event.getFailureCause());        }}
到这里,FrameworkServlet 算是讲完了,接下来就要开始讲 DispatcherServlet 这个核心类了

DispatcherServlet

org.springframework.web.servlet.DispatcherServlet核心类,作为 Spring MVC 的核心类,承担调度器的脚色,协调各个组件进行工作,处置惩罚哀求,一起来揭开这神秘的面纱吧

静态代码块

private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";private static final Properties defaultStrategies;static {    // Load default strategy implementations from properties file.    // This is currently strictly internal and not meant to be customized by application developers.    try {        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);    }    catch (IOException ex) {        throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());    }}
会从 DispatcherServlet.properties 文件中加载默认的组件实现类,将相关配置加载到 defaultStrategies 中,文件如下:

### org.springframework.web.servlet.DispatcherServlet.properties# Default implementation classes for DispatcherServlet's strategy interfaces.# Used as fallback when no matching beans are found in the DispatcherServlet context.# Not meant to be customized by application developers.org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\        org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\        org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\        org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\        org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\        org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager# Default implementation classes for DispatcherServlet's strategy interfaces.# Used as fallback when no matching beans are found in the DispatcherServlet context.# Not meant to be customized by application developers.org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\        org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\        org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\        org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\        org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\        org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
可以看到各个组件的默认实现类

构造方法

/** MultipartResolver used by this servlet. multipart 数据(文件)处置惩罚器 */@Nullableprivate MultipartResolver multipartResolver;/** LocaleResolver used by this servlet. 语言处置惩罚器,提供国际化的支持 */@Nullableprivate LocaleResolver localeResolver;/** ThemeResolver used by this servlet. 主题处置惩罚器,设置需要应用的整体样式 */@Nullableprivate ThemeResolver themeResolver;/** List of HandlerMappings used by this servlet. 处置惩罚器匹配器,返回哀求对应的处置惩罚器和拦截器们 */@Nullableprivate List handlerMappings;/** List of HandlerAdapters used by this servlet. 处置惩罚器适配器,用于执行处置惩罚器 */@Nullableprivate List handlerAdapters;/** List of HandlerExceptionResolvers used by this servlet. 非常处置惩罚器,用于剖析处置惩罚器发生的非常 */@Nullableprivate List handlerExceptionResolvers;/** RequestToViewNameTranslator used by this servlet. 视图名称转换器 */@Nullableprivate RequestToViewNameTranslator viewNameTranslator;/** FlashMapManager used by this servlet. FlashMap 管理器,负责重定向时保存参数到临时存储(默认 Session)中 */@Nullableprivate FlashMapManager flashMapManager;/** List of ViewResolvers used by this servlet. 视图剖析器,根据视图名称和语言,获取 View 视图 */@Nullableprivate List viewResolvers;public DispatcherServlet() {    super();    setDispatchOptionsRequest(true);}public DispatcherServlet(WebApplicationContext webApplicationContext) {    super(webApplicationContext);    setDispatchOptionsRequest(true);}
界说了九个组件,在组件预览中已经做过简单介绍了

构造方法中都会设置 dispatchOptionsRequesttrue,在父类 FrameworkServlet 中可以看到,如果哀求是 OPTIONS 则会处置惩罚哀求

onRefresh

onRefresh(ApplicationContext context) 方法,初始化 Spring MVC 的各个组件,方法如下:

@Overrideprotected void onRefresh(ApplicationContext context) {    initStrategies(context);}/** * Initialize the strategy objects that this servlet uses. *
May be overridden in subclasses in order to initialize further strategy objects. */protected void initStrategies(ApplicationContext context) {    // 初始化 MultipartResolver    initMultipartResolver(context);    // 初始化 LocaleResolver    initLocaleResolver(context);    // 初始化 ThemeResolver    initThemeResolver(context);    // 初始化 HandlerMapping    initHandlerMappings(context);    // 初始化 HandlerAdapter    initHandlerAdapters(context);    // 初始化 HandlerExceptionResolver    initHandlerExceptionResolvers(context);    // 初始化 RequestToViewNameTranslator    initRequestToViewNameTranslator(context);    // 初始化 ViewResolver    initViewResolvers(context);    // 初始化 FlashMapManager    initFlashMapManager(context);}
创建 Servlet WebApplicationContext 容器后会触发该方法,在《WebApplicationContext 容器的初始化》FrameworkServlet末节的 onRefresh 方法中提到过

可以看到每个方法会初始化构造方法中的每个组件

1. doService

doService(HttpServletRequest request, HttpServletResponse response)方法,DispatcherServlet 的处置惩罚哀求的入口方法,代码如下:

@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {    //  如果日志级别为 DEBUG,则打印哀求日志    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 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 属性中    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 的相关配置    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);            }        }    }}2. doDispatch【核心】

doDispatch(HttpServletRequest request, HttpServletResponse 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 {            //  检测哀求是否为上传哀求,如果是则通过 multipartResolver 将其封装成 MultipartHttpServletRequest 对象            processedRequest = checkMultipart(request);            multipartRequestParsed = (processedRequest != request);            // Determine handler for the current request.            //  得到哀求对应的 HandlerExecutionChain 对象(HandlerMethod 和 HandlerInterceptor 拦截器们)            mappedHandler = getHandler(processedRequest);            if (mappedHandler == null) { //  如果获取不到,则根据配置抛出非常或返回 404 错误                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.            //  处置惩罚有Last-Modified哀求头的场景            String method = request.getMethod();            boolean isGet = "GET".equals(method);            if (isGet || "HEAD".equals(method)) { // 不清楚为什么要判断方法范例为'HEAD'                // 获取哀求中服务器端末了被修改时间                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {                    return;                }            }            //  前置处置惩罚 拦截器            // 留意:该方法如果有一个拦截器的前置处置惩罚返回false,则开始倒序触发所有的拦截器的 已完成处置惩罚            if (!mappedHandler.applyPreHandle(processedRequest, response)) {                return;            }            // Actually invoke the handler.            //  真正的调用 handler 方法,也就是执行对应的方法,并返回视图            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());            //  如果是异步            if (asyncManager.isConcurrentHandlingStarted()) {                return;            }            //  无视图的情况下设置默认视图名称            applyDefaultViewName(processedRequest, mv);            //  后置处置惩罚 拦截器            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);        }        //  处置惩罚正常和非常的哀求调用效果        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);    }    catch (Exception ex) {        //  已完成处置惩罚 拦截器        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);    }    catch (Throwable err) {        //  已完成处置惩罚 拦截器        triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));    }    finally {        //  Asyn        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 处置惩罚哀求的整个流程步骤都列出来了,涉及到的组件分别在后续的文档中将分开进行分析

2.1 checkMultipart

checkMultipart(HttpServletRequest request) 方法,检测哀求是否为上传哀求,如果是则通过 multipartResolver 组件将其封装成 MultipartHttpServletRequest 对象

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {    // 如果该哀求是一个涉及到 multipart (文件)的哀求    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 {                // 将 HttpServletRequest 哀求封装成 MultipartHttpServletRequest 对象,剖析哀求内里的参数以及文件                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.2 getHandler

getHandler(HttpServletRequest request) 方法,通过 HandlerMapping 组件得到哀求对应的 HandlerExecutionChain 处置惩罚器执行链,包含 HandlerMethod 处置惩罚器和 HandlerInterceptor 拦截器们

@Nullableprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {    if (this.handlerMappings != null) {        for (HandlerMapping mapping : this.handlerMappings) {            HandlerExecutionChain handler = mapping.getHandler(request);            if (handler != null) {                return handler;            }        }    }    return null;}2.3 getHandlerAdapter

getHandlerAdapter(Object handler) 方法,获恰当前处置惩罚器对应的 HandlerAdapter 适配器对象

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

applyDefaultViewName(HttpServletRequest request, ModelAndView mv) 方法,ModelAndView 不为空,但是没有视图,则设置默认视图名称

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);        }    }}@Nullableprotected String getDefaultViewName(HttpServletRequest request) throws Exception {    // 利用到了 `viewNameTranslator` 视图名称转换器组件    return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);}2.5 processDispatchResult

processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) 方法,处置惩罚正常非常的哀求调用效果,方法如下:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,        @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,        @Nullable Exception exception) throws Exception {    //  标记是否为处置惩罚生成非常的 ModelAndView 对象    boolean errorView = false;    //  如果该哀求出现非常    if (exception != null) {        // 情况一,从 ModelAndViewDefiningException 中得到 ModelAndView 对象        if (exception instanceof ModelAndViewDefiningException) {            logger.debug("ModelAndViewDefiningException encountered", exception);            mv = ((ModelAndViewDefiningException) exception).getModelAndView();        }        // 情况二,处置惩罚非常,生成 ModelAndView 对象        else {            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);            mv = processHandlerException(request, response, handler, exception);            // 标记 errorView            errorView = (mv != null);        }    }    // Did the handler return a view to render?    //  是否进行页面渲染    if (mv != null && !mv.wasCleared()) {        //  渲染页面        render(mv, request, response);        //  清理哀求中的错误消息属性        // 因为上述的情况二中 processHandlerException 会通过 WebUtils 设置错误消息属性,所以这里得清理一下        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) {        mappedHandler.triggerAfterCompletion(request, response, null);    }}2.5.1 processHandlerException

processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,处置惩罚非常,生成 ModelAndView 对象

@Nullableprotected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,        @Nullable Object handler, Exception ex) throws Exception {    // Success and error responses may use different content types    // 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);    // Check registered HandlerExceptionResolvers...    //  遍历 HandlerExceptionResolver 数组,剖析非常,生成 ModelAndView 对象    ModelAndView exMv = null;    if (this.handlerExceptionResolvers != null) {        // 遍历 HandlerExceptionResolver 数组        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {            // 剖析非常,生成 ModelAndView 对象            exMv = resolver.resolveException(request, response, handler, ex);            // 生成成功,结束循环            if (exMv != null) {                break;            }        }    }    // <b> 情况一,生成了 ModelAndView 对象,进行返回    if (exMv != null) {        // ModelAndView 对象为空,则返回 null        if (exMv.isEmpty()) {            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);            return null;        }        // We might still need view name translation for a plain error model...        // 没有视图则设置默认视图        if (!exMv.hasView()) {            String defaultViewName = getDefaultViewName(request);            if (defaultViewName != null) {                exMv.setViewName(defaultViewName);            }        }        // 打印日志        if (logger.isTraceEnabled()) {            logger.trace("Using resolved error view: " + exMv, ex);        }        if (logger.isDebugEnabled()) {            logger.debug("Using resolved error view: " + exMv);        }        // 设置哀求中的错误消息属性        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());        return exMv;    }    //  情况二,未生成 ModelAndView 对象,则抛出非常    throw ex;}
处,遍历 HandlerExceptionResolver 数组,调用 HandlerExceptionResolver#resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,剖析非常,生成 ModelAndView 对象

<b> 处,情况一,生成了 ModelAndView 对象,逻辑比较简单

处,情况二,未生成 ModelAndView 对象,则抛出非常

2.5.2 render

render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) 方法,渲染 ModelAndView,方法如下:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {    // Determine locale for request and apply it to the response.    //  剖析 request 中得到 Locale 对象,并设置到 response 中    Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());    response.setLocale(locale);    // 得到 View 对象    View view;    String viewName = mv.getViewName();    // 情况一,利用 viewName 得到 View 对象    if (viewName != null) {        // We need to resolve the view name.        //  利用 viewName 得到 View 对象        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);        if (view == null) { // 获取不到,抛出 ServletException 非常            throw new ServletException("Could not resolve view with name &#39;" + mv.getViewName() +                    "&#39; in servlet with name &#39;" + getServletName() + "&#39;");        }    }    // 情况二,直接利用 ModelAndView 对象的 View 对象    else {        // No need to lookup: the ModelAndView object contains the actual View object.        //  直接利用 ModelAndView 对象的 View 对象        view = mv.getView();        if (view == null) { // 获取不到,抛出 ServletException 非常            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +                    "View object in servlet with name &#39;" + getServletName() + "&#39;");        }    }    // Delegate to the View object for rendering.    // 打印日志    if (logger.isTraceEnabled()) {        logger.trace("Rendering view [" + view + "] ");    }    try {        //  设置响应的状态码        if (mv.getStatus() != null) {            response.setStatus(mv.getStatus().value());        }        //  渲染页面        view.render(mv.getModelInternal(), request, response);    }    catch (Exception ex) {        if (logger.isDebugEnabled()) {            logger.debug("Error rendering view [" + view + "]", ex);        }        throw ex;    }}总结

本文对 Spring MVC 处置惩罚哀求的整个过程进行了分析,核心就是通过 DispatcherServlet 协调各个组件工作,处置惩罚哀求,因为 DispatcherServlet 是一个 Servlet,在 Servlet 容器中,会将哀求交由它来处置惩罚。

通过本文对 DispatcherServlet 是怎样处置惩罚哀求已经有了一个整体的认识,不外在整个处置惩罚过程中涉及到的各个 Spring MVC 组件还没有进行分析,对于许多细节存在疑惑,不要慌,那么接下来会对每一个 Spring MVC 组件进行分析。如许,便于我们对 Spring MVC 的理解,然后再回过头来思索 DispatcherServlet 这个类,可以或许更好的将这些组件串联在一起。先整体,后局部,渐渐渐渐抽丝剥茧,看清理透。

<hr>
流程示意图,来自 SpringMVC - 运行流程图及原理分析

代码序列图

流程示意图,来自《看透 Spring MVC 源代码分析与实践》 册本中的第 123 页


作者: 亦是如此real    时间: 2021-4-23 12:53
转发了
作者: 三分天注定丶I3    时间: 2021-4-23 13:17
转发了
作者: 工地搬砖不清闲    时间: 2021-4-23 13:42
转发了
作者: lchang    时间: 2021-4-23 17:30
转发了
作者: Kevin56160527    时间: 2021-4-23 18:58
转发了
作者: 一往无前风雨兼程    时间: 2021-4-23 19:37
转发了
作者: 困罗晋    时间: 2021-4-23 21:13
转发了
作者: 梦回大秦2020    时间: 2021-4-23 21:59
转发了
作者: ITBOY南有先生    时间: 2021-4-23 22:44
转发了
作者: 李钊平72616235    时间: 2021-4-23 22:51
转发了
作者: 北平等你01    时间: 2021-4-24 09:24
转发了
作者: 追风ly    时间: 2021-4-24 09:34
转发了
作者: liaohaibowx    时间: 2021-4-24 12:29
转发了
作者: 正义呼唤我    时间: 2021-4-25 12:01
转发了




欢迎光临 创意电子 (https://www.wxcydz.cc/) Powered by Discuz! X3.4