Spring MVC之 一次请求响应的过程

news2024/11/16 7:34:03
Spring MVC 会创建两个容器,其中创建 Root WebApplicationContext 后,调用其 refresh()方法会触发刷新事件,完成 Spring IOC 初始化相关工作,会初始化各种 Spring Bean 到当前容器中
我们先来了解一个请求是如何被 Spring MVC 处理的,由于整个流程涉及到的代码非常多,所以本文的重点在于解析 整体的流程,主要讲解 DispatcherServlet 这个核心类,弄懂了这个流程后,才能更好的理解具体的源码,回过头再来看则会更加的豁然开朗

Spring MVC 处理请求的流程大致如上图所示

  1. 用户的浏览器发送一个请求,这个请求经过互联网到达了我们的服务器。Servlet 容器首先接待了这个请求,并将该请求委托给 DispatcherServlet 进行处理。

  1. DispatcherServlet 将该请求传给了处理器映射组件 HandlerMapping,并获取到适合该请求的 HandlerExecutionChain 拦截器和处理器对象。

  1. 在获取到处理器后,DispatcherServlet 还不能直接调用处理器的逻辑,需要进行对处理器进行适配。处理器适配成功后,DispatcherServlet 通过处理器适配器 HandlerAdapter 调用处理器的逻辑,并获取返回值 ModelAndView 对象。

  1. 之后,DispatcherServlet 需要根据 ModelAndView 解析视图。解析视图的工作由 ViewResolver 完成,若能解析成功,ViewResolver 会返回相应的 View 视图对象。

  1. 在获取到具体的 View 对象后,最后一步要做的事情就是由 View 渲染视图,并将渲染结果返回给用户。

以上就是 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 负责协调,其他组件则各自做分内之事,互不干扰。经过这样的职责划分,代码会便于维护。同时对于源码阅读者来说,也会很友好。可以降低理解源码的难度,使大家能够快速理清主逻辑。这一点值得我们学习。

FrameworkServlet

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

FrameworkServlet 覆盖了 HttpServlet 的以下方法:

  • doGet(HttpServletRequest request, HttpServletResponse response)

  • doPost(HttpServletRequest request, HttpServletResponse response)

  • doPut(HttpServletRequest request, HttpServletResponse response)

  • doDelete(HttpServletRequest request, HttpServletResponse response)

  • doOptions(HttpServletRequest request, HttpServletResponse response)

  • doTrace(HttpServletRequest request, HttpServletResponse response)

  • service(HttpServletRequest request, HttpServletResponse response)

这些方法分别处理不同 HTTP 请求类型的请求,最终都会调用另一个 processRequest(HttpServletRequest request, HttpServletResponse response) 方法

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

service

service(HttpServletRequest request, HttpServletResponse response) 方法,用于处理请求,方法如下

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // <1> 获得请求方法
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    // <2> 处理 PATCH 请求
    if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
        processRequest(request, response);
    }
    // <3> 处理其他类型的请求
    else {
        super.service(request, response);
    }
}
  1. 获得 HttpMethod 请求方法

  1. 若请求方法是 PATCH ,调用 processRequest(HttpServletRequest request, HttpServletResponse response) 方法,处理请求。

因为 HttpServlet 默认没提供处理 Patch 请求类型的请求 ,所以只能通过DispatcherServlet覆盖父类的 service 方法来实现

  1. 如果是其他类型的请求,则直接调用父类的 service 方法,该方法如下:

// HttpServlet.java
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    if (method.equals(METHOD_GET)) {
        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 {
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < lastModified) {
                // 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);
    }
}

可能你会有疑惑,为什么不在 service(HttpServletRequest request, HttpServletResponse response) 方法,直接调用 processRequest(HttpServletRequest request, HttpServletResponse response) 方法就好了?因为针对不同的请求方法,处理略微有所不同,父类 HttpServlet 已经处理了。

doOptions

doOptions(HttpServletRequest request, HttpServletResponse response)方法,用于处理 OPTIONS 类型的请求,方法如下:

@Override
protected 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 {

    // <1> 记录当前时间,用于计算处理请求花费的时间
    long startTime = System.currentTimeMillis();
    // <2> 记录异常,用于保存处理请求过程中发送的异常
    Throwable failureCause = null;

    // <3> TODO
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    // <4> TODO
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    // <5> TODO
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    // <6> TODO
    initContextHolders(request, localeContext, requestAttributes);

    try {
        // <7> 执行真正的逻辑
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex; // <8> 记录抛出的异常
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex; // <8> 记录抛出的异常
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        // <9> TODO
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        // <10> TODO
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        // <11> 如果日志级别为 DEBUG,则打印请求日志
        logResult(request, response, failureCause, asyncManager);
        // <12> 发布 ServletRequestHandledEvent 请求处理完成事件
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

<1> 记录当前时间,用于计算处理请求花费的时间

<2> 记录异常,用于保存处理请求过程中发送的异常

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

<8> 记录执行过程抛出的异常,最终在 finally 的代码段中使用。

<11> 如果日志级别为 DEBUG,则打印请求日志

<12> 调用 publishRequestHandledEvent 方法,通过 WebApplicationContext 发布 ServletRequestHandledEvent 请求处理完成事件,目前好像 Spring MVC 没有监听这个事件,可以自己写一个监听器用于获取请求信息,示例如下:

@Component
@Log4j2
public class ServletRequestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent>{
    @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 中,文件如下:

可以看到各个组件的默认实现类

构造方法

/** MultipartResolver used by this servlet. multipart 数据(文件)处理器 */
@Nullable
private MultipartResolver multipartResolver;

/** LocaleResolver used by this servlet. 语言处理器,提供国际化的支持 */
@Nullable
private LocaleResolver localeResolver;

/** ThemeResolver used by this servlet. 主题处理器,设置需要应用的整体样式 */
@Nullable
private ThemeResolver themeResolver;

/** List of HandlerMappings used by this servlet. 处理器匹配器,返回请求对应的处理器和拦截器们 */
@Nullable
private List<HandlerMapping> handlerMappings;

/** List of HandlerAdapters used by this servlet. 处理器适配器,用于执行处理器 */
@Nullable
private List<HandlerAdapter> handlerAdapters;

/** List of HandlerExceptionResolvers used by this servlet. 异常处理器,用于解析处理器发生的异常 */
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;

/** RequestToViewNameTranslator used by this servlet. 视图名称转换器 */
@Nullable
private RequestToViewNameTranslator viewNameTranslator;

/** FlashMapManager used by this servlet. FlashMap 管理器,负责重定向时保存参数到临时存储(默认 Session)中 */
@Nullable
private FlashMapManager flashMapManager;

/** List of ViewResolvers used by this servlet. 视图解析器,根据视图名称和语言,获取 View 视图 */
@Nullable
private List<ViewResolver> viewResolvers;

public DispatcherServlet() {
    super();
    setDispatchOptionsRequest(true);
}

public DispatcherServlet(WebApplicationContext webApplicationContext) {
    super(webApplicationContext);
    setDispatchOptionsRequest(true);
}

定义了九个组件,在组件预览中已经做过简单介绍了

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

onRefresh

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

@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>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 的处理请求的入口方法,代码如下

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // <1> 如果日志级别为 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.
    // <2> 保存当前请求中相关属性的一个快照
    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.
    // <3> 设置 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());

    // <4> 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 {
        // <5> 执行请求的分发
        doDispatch(request, response);
    }
    finally {
        // <6> 异步处理相关
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}
  1. 调用logRequest(HttpServletRequest request) 方法,如果日志级别为 DEBUG,则打印请求日志

  1. 保存当前请求中相关属性的一个快照,作为异步处理的属性值,防止被修改,暂时忽略

  1. 设置 Spring 框架中的常用对象到 request 属性中,例如 webApplicationContextlocaleResolverthemeResolver

  1. FlashMap 的相关配置,暂时忽略

  1. 【重点】调用 doDispatch(HttpServletRequest request, HttpServletResponse response) 方法,执行请求的分发

  1. 异步处理相关,暂时忽略

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;

    // <1> 获取异步管理器
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            // <2> 检测请求是否为上传请求,如果是则通过 multipartResolver 将其封装成 MultipartHttpServletRequest 对象
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            // <3> 获得请求对应的 HandlerExecutionChain 对象(HandlerMethod 和 HandlerInterceptor 拦截器们)
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) { // <3.1> 如果获取不到,则根据配置抛出异常或返回 404 错误
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            // <4> 获得当前 handler 对应的 HandlerAdapter 对象
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            // <4.1> 处理有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;
                }
            }

            // <5> 前置处理 拦截器
            // 注意:该方法如果有一个拦截器的前置处理返回false,则开始倒序触发所有的拦截器的 已完成处理
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // <6> 真正的调用 handler 方法,也就是执行对应的方法,并返回视图
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            // <7> 如果是异步
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            // <8> 无视图的情况下设置默认视图名称
            applyDefaultViewName(processedRequest, mv);
            // <9> 后置处理 拦截器
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex; // <10> 记录异常
        }
        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);
        }
        // <11> 处理正常和异常的请求调用结果
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        // <12> 已完成处理 拦截器
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        // <12> 已完成处理 拦截器
        triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));
    }
    finally {
        // <13.1> Asyn
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        // <13.1> 如果是上传请求则清理资源
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}
  1. 获得 WebAsyncManager 异步处理器,暂时忽略

  1. 【文件】调用 checkMultipart(HttpServletRequest request) 方法,检测请求是否为上传请求,如果是则通过 multipartResolver 组件将其封装成 MultipartHttpServletRequest 对象,便于获取参数信息以及文件

  1. 【处理器匹配器】调用 getHandler(HttpServletRequest request) 方法,通过 HandlerMapping 组件获得请求对应的 HandlerExecutionChain 处理器执行链,包含 HandlerMethod 处理器和 HandlerInterceptor 拦截器们

  1. 如果获取不到对应的执行链,则根据配置抛出异常或返回 404 错误

  1. 【处理器适配器】调用 getHandlerAdapter(Object handler) 方法,获得当前处理器对应的 HandlerAdapter 适配器对象

  1. 处理有 Last-Modified 请求头的场景,暂时忽略

  1. 【拦截器】调用 HandlerExecutionChain 执行链的 applyPreHandle(HttpServletRequest request, HttpServletResponse response) 方法,拦截器的前置处理

如果出现拦截器前置处理失败,则会调用拦截器的已完成处理方法(倒序)

  1. 【重点】调用 HandlerAdapter 适配器的 handle(HttpServletRequest request, HttpServletResponse response, Object handler) 方法,真正的执行处理器,也就是执行对应的方法(例如我们定义的 Controller 中的方法),并返回视图

  1. 如果是异步,则直接 return注意,还是会执行 finally 中的代码

  1. 调用 applyDefaultViewName(HttpServletRequest request, ModelAndView mv) 方法,ModelAndView 不为空,但是没有视图,则设置默认视图名称,使用到了 viewNameTranslator 视图名称转换器组件

  1. 【拦截器】调用 HandlerExecutionChain 执行链的 applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) 方法,拦截器的后置处理倒序

  1. 记录异常,注意,此处仅仅记录,不会抛出异常,而是统一交给 <11> 处理

  1. 【处理执行结果】调用 processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) 方法,处理正常异常的请求调用结果,包含页面渲染

  1. 【拦截器】如果上一步发生了异常,则调用 triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, Exception ex) 方法,即调用 HandlerInterceptor 执行链的 triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) 方法,拦截器已完成处理倒序

  1. finally 代码块,异步处理,暂时忽略,如果是涉及到文件的请求,则清理相关资源

上面将 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 拦截器们

@Nullable
protected 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);
        }
    }
}
@Nullable
protected 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 {

    // <1> 标记是否为处理生成异常的 ModelAndView 对象
    boolean errorView = false;

    // <2> 如果该请求出现异常
    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?
    // <3> 是否进行页面渲染
    if (mv != null && !mv.wasCleared()) {
        // <3.1> 渲染页面
        render(mv, request, response);
        // <3.2> 清理请求中的错误消息属性
        // 因为上述的情况二中 processHandlerException 会通过 WebUtils 设置错误消息属性,所以这里得清理一下
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    // <4> 如果是异步
    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    // <5> 已完成处理 拦截器
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}
  1. 标记是否为处理生成异常的 ModelAndView 对象

  1. 如果该请求出现异常

  1. 情况一,从 ModelAndViewDefiningException 中获得 ModelAndView 对象

  1. 情况二,调用 processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,处理异常,生成 ModelAndView 对象

  1. 如果 ModelAndView 不为空且没有被清理,例如你现在使用最多的 @ResponseBody 这里就为空,不需要渲染

  1. 调用 render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) 方法,渲染页面

  1. 如果是上面第 2 步中情况二生成的 ModelAndView 对象,则需要清理请求中的错误消息属性,因为上述的情况二会通过 WebUtils 设置错误消息属性,所以这里得清理一下

  1. 如果是异步请求,则直接 return

  1. 正常情况下,调用 triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, Exception ex) 方法,即调用 HandlerInterceptor 执行链的 triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) 方法,拦截器已完成处理倒序

2.5.1 processHandlerException

processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,处理异常,生成 ModelAndView 对象

@Nullable
protected 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...
    // <a> 遍历 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;
    }

    // <c> 情况二,未生成 ModelAndView 对象,则抛出异常
    throw ex;
}

<a> 处,遍历 HandlerExceptionResolver 数组,调用 HandlerExceptionResolver#resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,解析异常,生成 ModelAndView 对象

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

<c> 处,情况二,未生成 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.
    // <1> 解析 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.
        // <2.1> 使用 viewName 获得 View 对象
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) { // 获取不到,抛出 ServletException 异常
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                    "' in servlet with name '" + getServletName() + "'");
        }
    }
    // 情况二,直接使用 ModelAndView 对象的 View 对象
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        // <2.2> 直接使用 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 '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    // 打印日志
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view [" + view + "] ");
    }
    try {
        // <3> 设置响应的状态码
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        // <4> 渲染页面
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "]", ex);
        }
        throw ex;
    }
}
  1. 调用 LocaleResolverresolveLocale(HttpServletRequest request) 方法,从 request 中获得 Locale 对象,并设置到 response

  1. 获得 View 对象,有两种情况

  1. 调用 resolveViewName 方法,使用 viewName 通过获得 View 对象,方法如下

@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
        Locale locale, HttpServletRequest request) throws Exception {

    if (this.viewResolvers != null) {
        // 遍历 ViewResolver 数组
        for (ViewResolver viewResolver : this.viewResolvers) {
            // 根据 viewName + locale 参数,解析出 View 对象
            View view = viewResolver.resolveViewName(viewName, locale);
            // 解析成功,直接返回 View 对象
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}
  1. 直接使用 ModelAndView 对象的 View 对象

  1. 设置响应的状态码

  1. 调用 Viewrender(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) 方法,渲染视图

总结

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

通过本文对 DispatcherServlet 是如何处理请求已经有了一个整体的认识,不过在整个处理过程中涉及到的各个 Spring MVC 组件还没有进行分析,对于许多细节存在疑惑,那么接下来会对每一个 Spring MVC 组件进行分析。这样,便于我们对 Spring MVC 的理解,然后再回过头来思考 DispatcherServlet 这个类,能够更好的将这些组件串联在一起。先整体,后局部,逐步逐步抽丝剥茧,理解理透。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/356184.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023最新文件快递柜系统网站源码 | 匿名口令分享 | 临时文件分享

内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示三、学习资料下载一、详细介绍 2023最新文件快递柜系统网站源码 | 匿名口令分享 | 临时文件分享 很多时候&#xff0c;我们都想将一些文件或文本传送给别人&#xff0c;或者跨端传递一些信息&#xff0c;但是我们又不…

自抗扰控制ADRC之三种微分跟踪器TD仿真分析

目录 前言 1 全程快速微分器 1.1仿真分析 1.2仿真模型 1.3仿真结果 1.4结论 2 Levant微分器 2.1仿真分析 2.2仿真模型 2.3仿真结果 3.非线性跟踪微分器——韩教授 3.1仿真分析 3.2小结 4.总结 前言 工程上信号的微分是难以得到的&#xff0c;所以本文采用微分器…

重磅 | 小O软件新品【鲸鱼地图】发布

千呼万唤始出来.......&#xff0c;小O系列软件又添新品【鲸鱼地图】&#xff01;&#xff01;&#xff01; 2023年新年伊始&#xff0c;小O就投入到新品研发工作中&#xff0c;秉承“发现地理价值”理念&#xff0c;为用户提供更加好用、易用的地图软件产品&#xff0c;经过春…

【C语言】编程初学者入门训练(完结)

文章目录1. 实践出真知2. 我是大V3. 有容乃大4. 小飞机5. 缩短2进制6. 十六进制转十进制7. printf的返回值8. 成绩输入输出9. 学生基本信息输入输出10. 字符圣诞树11. ASCII码12. 出生日期输入输出13. 按照格式输入并交换输出14. 字符转ASCII码15. 计算表达式的值16. 计算带余除…

Java特性之设计模式【策略模式】

一、策略模式 概述 在策略模式&#xff08;Strategy Pattern&#xff09;中&#xff0c;一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式 在策略模式中&#xff0c;我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略…

Matlab数学建模常用算法及论文插图绘制模板资源合集

最近有很多朋友咨询我关于Matlab论文插图绘制方面的问题。 问了一下&#xff0c;这些朋友中&#xff0c;除了写博士论文的&#xff0c;大部分都是要参加美赛的。 这让我突然想起&#xff0c;自己曾经为了水论文&#xff0c;购买过一批Matlab数学建模的资料。 想了想&#xf…

EMIF转AXI-Lite接口

最近想用DSP对FPGA里的IP进行配置&#xff0c;感觉没有什么特别好的办法。如果能像Zynq一样直接有能够配置外设的AXI-Lite接口就好了。EMIF是DSP的外部存储器访问接口&#xff0c;支持对存储器的同步或异步访问。在我现有的条件下&#xff0c;利用EMIF接口配置FPGA内部的寄存器…

2023河南省第二届职业技能大赛郑州市选拔赛“网络安全”项目比赛样题任务书

2023河南省第二届职业技能大赛郑州市选拔赛“网络安全” 项目比赛样题任务书 一、竞赛时间 共计360分钟。 竞赛任务书内容 2023河南省第二届职业技能大赛郑州市选拔赛“网络安全” 项目比赛样题任务书 A模块基础设施设置/安全加固&#xff08;200分&#xff09; A-1&…

如何安全存储企业文件?

信息时代的到来&#xff0c;企业文件的安全存储越来越被企业管理者看重。常见的企业文件存储是借用第三方工具&#xff0c;如企业网盘&#xff0c;这种方法性价比高&#xff0c;而且也比较安全&#xff0c;被各大企业所喜爱。市面上的企业网盘类工具不胜枚举&#xff0c;该如何…

O(1)调度器:Linux2.6版本的核心算法

上一章学习了O(n)调度器的设计&#xff0c;以及它的核心算法&#xff0c;其主要思路如下&#xff1a; O(n)调度器采用一个Runqueue运行队列来管理所有可运行的进程&#xff0c;在主调度schedule函数中选择一个优先级最高&#xff0c;也就是时间片最大的进程来运行&#xff0c;…

2019蓝桥杯真题完全二叉树的权值 C语言/C++

题目描述 给定一棵包含 N个节点的完全二叉树&#xff0c;树上每个节点都有一个权值&#xff0c;按从 上到下、从左到右的顺序依次是 A_1, A_2, A_N&#xff0c;如下图所示&#xff1a; 现在小明要把相同深度的节点的权值加在一起&#xff0c;他想知道哪个深度的节点 权值之和最…

力扣2241. 设计一个 ATM 机器

力扣上的一个中等难度的题&#xff0c;之所以写一篇博客记录下来&#xff0c;是因为貌似触发了力扣的彩蛋&#xff0c;第一次遇见&#xff0c;感觉挺有意义的。 题目如下&#xff1a; 一个 ATM 机器&#xff0c;存有 5 种面值的钞票&#xff1a;20 &#xff0c;50 &#xff0c…

手把手教你使用gdb调试器

所谓调试&#xff0c;指的是对编好的程序用各种手段进进行查错和排非错的过程。进行这种查错处理时&#xff0c;下面将讲解如何使用gdb进行程序的调试。 gdb 简介 gdb是一个功能强大的调试工具&#xff0c;可以用来调试C程序或C程序。在使用这个工具进行程序调试时&#xff0…

nodejs学习-4:nodejs连接mongodb和相关操作

1. express生成器生成express模板 前提需要首先下载好&#xff1a;express-generator&#xff0c;命令如下(全局安装) npm install -g express-generator生成模板命令如下&#xff1a; express 项目名称 --viewejs // --view 参数表示前端界面使用的引擎&#xff0c;这里使用…

Java线程池运行原理,线程池源码解读【Java线程池学习二】

一、前奏 有了上一篇博文的学习&#xff0c;相信你对于线程池的使用这块已经不在存在什么问题了&#xff0c;日常开发和面试也都足够了。 线程池最优使用策略【Java线程池学习一】 但随着时间的推移在闲下来的时候我突然想&#xff0c;当任务进入了队列之后是怎么取出来的呢…

linux系统根文件系统构建

根文件系统构建 一、根文件系统简介 根文件系统是 Linux 内核启动以后挂载(mount)的第一个文件系统&#xff0c;从根文件系统中读取初始化脚本&#xff0c;比如 rcS&#xff0c;inittab 等。根文件系统和 Linux 内核是分开的&#xff0c;单独的 Linux 内核是没法正常工作的&a…

快捷获取GDI+绘图参数的两种经验方案

文章目录一、使用系统的枚举二、专用枚举1、颜色Color2、字体Font3、字体名称4、笔刷Brush5、笔Pen6、矩形Rectangle7、点Point8、大小Size文章出处&#xff1a; https://blog.csdn.net/haigear/article/details/129085403在绘图中&#xff0c;常常需要给出颜色&#xff0c;字体…

目标检测各常见评价指标详解

注&#xff1a;本文仅供学习&#xff0c;未经同意请勿转载 说明&#xff1a;该博客来源于xiaobai_Ry:2020年3月笔记 对应的PDF下载链接在&#xff1a;待上传 目录 常见的评价指标 准确率 &#xff08;Accuracy&#xff09; 混淆矩阵 &#xff08;Confusion Matrix&#xff…

SpringBoot实现统一返回接口(除AOP)

起因 关于使用AOP去实现统一返回接口在之前的博客中我们已经实现了&#xff0c;但我突然突发奇想&#xff0c;SpringBoot中异常类的统一返回好像是通过RestControllerAdvice 这个注解去完成的&#xff0c;那我是否也可以通过这个注解去实现统一返回接口。 正文 这个方法主要…

Django框架之模型视图--HttpResponse对象

HttpResponse对象 视图在接收请求并处理后&#xff0c;必须返回HttpResponse对象或子对象。HttpRequest对象由Django创建&#xff0c;HttpResponse对象由开发人员创建。 1 HttpResponse 可以使用django.http.HttpResponse来构造响应对象。 HttpResponse(content响应体, con…