目录

Spring-MVC源码分析请求处理流程

Spring MVC源码分析の请求处理流程


前言

在Spring MVC中,请求处理的关键是 DispatcherServletdoDispatch 方法:

https://i-blog.csdnimg.cn/direct/f8142b60515f4daaac0f83c6f98c1384.png 其中包含了@RequestBody,@ResponseBody等注解的解析,参数的适配,方法拦截器的执行,目标方法的执行,返回值处理,以及异常处理等逻辑。

Spring MVC整体请求流程,本篇只简单地进行主要流程的分析,诸如如何解析参数,解析返回值,渲染视图等不在此列。

客户端请求

DispatcherServlet

getHandler(request)

HandlerExecutionChain(包含Handler和拦截器)

调用拦截器 preHandle()

调用 Controller 方法

调用拦截器 postHandle()

返回视图、渲染

调用拦截器 afterCompletion()

一、doDispatch

1.1、检查文件上传

当某个请求到达 doDispatch 时,首先会检查其是否为 文件格式

https://i-blog.csdnimg.cn/direct/23fb970df33948beae5d569cf91b2d66.png 假设我在controller层中进行文件上传的请求处理:

@RestController
public class TestController {

    @Autowired
    private TestService testService;

    @RequestMapping(method = RequestMethod.POST, path = "/test")
    public void test(@RequestParam("file") MultipartFile file){
        System.out.println(file.getName());
    }
}

同时需要在 web.xml 中配置:

https://i-blog.csdnimg.cn/direct/bbe0ea5255214a20a62726b3038f7a14.png 还需要在 web.xml 所引用的 spring.xml 中进行配置:

https://i-blog.csdnimg.cn/direct/f8fd26b0d7b449ebb345b5dde493a29a.png 这样在进入 checkMultipart 方法时,首先会利用 multipartResolver 校验该请求的类型是否为文件上传:

https://i-blog.csdnimg.cn/direct/2f4c58cf4c6e46eba020d2e323b4d2fd.png 调用的是 StandardServletMultipartResolverisMultipart ,在该方法中,首先会获取请求头中的 Content-Type,如果用户在表单中使用 <form enctype="multipart/form-data"> ,那么 Content-Type 就是:multipart/form-data;然后会忽略大小写判断 Content-Type 是否以 multipart/form-data 开头。 https://i-blog.csdnimg.cn/direct/681f8ee6e44d4464b08179ab5fe46158.png 如果满足条件,则会进入 multipartResolverresolveMultipart 方法进行解析:

https://i-blog.csdnimg.cn/direct/45acd88d2dde44beaa9a92c17ef67577.png 调用的同样是 StandardServletMultipartResolverresolveMultipart ,关键代码,区分普通的form-data和文件类型的form-data:

  • 普通的form-data存入 multipartParameterNames 集合中。

  • 文件类型的form-data存入 multipartFiles 集合中。

    https://i-blog.csdnimg.cn/direct/9234ed57818e4c188ced2da618933658.png 如果是文件请求,最后会封装成 MultipartHttpServletRequest 的对象,返回到方法的调用处:

    https://i-blog.csdnimg.cn/direct/6408ec24fe8c4c9d855456c1f100cf21.png

1.2、得到Handler

继续向下执行,到 DispatcherServletgetHandler ,该方法的目的是:根据当前请求 URI,找到对应的 Handler(Controller 方法或对象),并包装为 HandlerExecutionChain(包含拦截器链)。

首先会遍历 DispatcherServlet.properties 中的三个默认 HandlerMapping

https://i-blog.csdnimg.cn/direct/3d3f0c3daf4148cda09924f4281bd234.png 再去调用各自的 getHandler 方法,目前案例中是 @Controller注解 的模式,所以利用的是 RequestMappingHandlerMapping ,其中的关键代码,利用请求路径, 去容器启动过程中收集的集合中找有无匹配的

https://i-blog.csdnimg.cn/direct/e537c4c2b8b24f6aa72932a26897e523.png lookupHandlerMethod ,会去 pathLookup 集合中找, pathLookup:key存放了请求路径,value存放了注解的信息。

https://i-blog.csdnimg.cn/direct/3b3d662c83dd4a4ebb2ee8251aaa0011.png 最终找到对应的方法对象并返回:

https://i-blog.csdnimg.cn/direct/0fce5d7b3af24d89ba6299729c0275e5.png https://i-blog.csdnimg.cn/direct/d88d76c58b0d4fa08b39fd875f90ebf2.png https://i-blog.csdnimg.cn/direct/605eb01c6105409fa3187efb648c5d8e.png 如果找不到匹配的,会在该方法中直接返回给浏览器404:

https://i-blog.csdnimg.cn/direct/8b313a165a00467d9ebcba302fec80b8.png getHandler 方法的整体逻辑,除了上图中的根据路径找Handler,还有组装拦截器和跨域请求的逻辑:

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 调用子类实现的 getHandlerInternal 方法,根据当前请求查找对应的 Handler(Controller 或 HandlerAdapter)
    Object handler = getHandlerInternal(request);

    // 如果没有找到对应的 Handler,则尝试获取默认 Handler(可在配置中设置)
    if (handler == null) {
        handler = getDefaultHandler();
    }

    // 如果仍然没有找到任何 Handler(也没有默认 Handler),则返回 null,DispatcherServlet 会响应 404
    if (handler == null) {
        return null;
    }

    // 如果 handler 是一个字符串(通常表示 Bean 名),通过 Spring 容器获取对应的 Bean 实例
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    // 确保 request 中缓存了 lookupPath(URI 去除 contextPath 的路径),供拦截器或后续处理使用
    if (!ServletRequestPathUtils.hasCachedPath(request)) {
        initLookupPath(request);
    }

    // 构建 HandlerExecutionChain,它包含了 handler 及所有匹配的 HandlerInterceptor 拦截器链
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    if (logger.isTraceEnabled()) {
        logger.trace("Mapped to " + handler);
    }

    else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
        logger.debug("Mapped to " + executionChain.getHandler());
    }

    // 判断是否 handler 提供了 CORS 配置,或者当前请求是 CORS 的预检请求(OPTIONS 请求)
    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        // 获取当前 handler 的 CORS 配置对象
        CorsConfiguration config = getCorsConfiguration(handler, request);
        // 如果配置了全局 CorsConfigurationSource,则合并全局 CORS 配置与当前配置
        if (getCorsConfigurationSource() != null) {
            CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
            config = (globalConfig != null ? globalConfig.combine(config) : config);
        }
        // 如果存在 CORS 配置,则进行配置合法性校验(如 allowCredentials 是否兼容 allowOrigins)
        if (config != null) {
            config.validateAllowCredentials();
        }
        // 将 CORS 拦截器添加到 HandlerExecutionChain 中,形成新的执行链
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    // 返回最终构建的 HandlerExecutionChain,包含 handler 和对应拦截器链,供 DispatcherServlet 后续调用
    return executionChain;
}

1.3、Handler适配

在拿到请求路径相对应的Handler后,就会进入 getHandlerAdapter 方法进行适配,同样是拿到 DispatcherServlet.properties 中的四个默认 HandlerAdapter

https://i-blog.csdnimg.cn/direct/d0815e28cd9644fdb6a3ce9d6e9702c8.png 与案例中适配的应该是 RequestMappingHandlerAdapter :

https://i-blog.csdnimg.cn/direct/51c4845ea09e4756bba874f6a4093fea.png 这一块的代码和Spring AOP的切面适配一样,都是 适配器模式 的体现。

1.4、执行拦截器preHandle方法

在拿到Handler对应的适配器之后,就会去执行自定义拦截器的preHandle方法(如果有的话),preHandle方法条件不匹配,就直接结束,不会调用目标方法:

https://i-blog.csdnimg.cn/direct/0e5a89abc1e84129b608cf39a4fe71d3.png 注意如果有多个拦截器, preHandle的顺序是从前往后执行

https://i-blog.csdnimg.cn/direct/bc97edd9fea749c1a2ea35d6dcbf94cb.png 并且不满足preHandle定义的条件,还会去执行自定义拦截器链中重写的 AfterCompletion 方法**(顺序是从后往前执行)**。 并且在执行的过程中抛出了异常,只会记录日志。

https://i-blog.csdnimg.cn/direct/b2d8a656b93041f5b87b2372bb36b85c.png

1.5、执行目标方法

如果拦截器链的preHandle方法条件满足,或者没有自定义拦截器,则会使用1.3中找到的适配器,调用其中的 handle ,执行目标方法:

https://i-blog.csdnimg.cn/direct/825e4d57c20f46e9a8835a33eb1eca22.png https://i-blog.csdnimg.cn/direct/82ee4d66de654b2c991430cf5e0cd42b.png 首先会初始化一个 ModelAndView ,然后进入关键代码 invokeHandlerMethod

https://i-blog.csdnimg.cn/direct/ba82275c1c024d4d877a8ef5bc8ca075.png 执行目标方法的是在 invokeHandlerMethodinvokeAndHandle :

https://i-blog.csdnimg.cn/direct/f121cedf034345a9b3accda29d467526.png invokeAndHandle :

https://i-blog.csdnimg.cn/direct/67083a3aeef04ebeaa2d37f7e4a20926.png invokeAndHandleinvokeForRequest :先获取请求参数,然后 通过反射 调用目标方法:

https://i-blog.csdnimg.cn/direct/735cb65c43984a7eb0e0747464f394ce.png 最后回到 invokeAndHandle ,设置返回状态码,并且使用返回值处理器对返回值进行处理:

https://i-blog.csdnimg.cn/direct/850465fb0b984d4380b3fc2785a957ed.png

1.6、执行拦截器PostHandle方法

在执行完目标方法之后,会执行自定义拦截器重写的PostHandle方法**(从后往前执行)**

https://i-blog.csdnimg.cn/direct/e9905d6e165046e7bd7bf5c3c2a04897.png

1.7、处理异常&渲染视图

如果在上述的执行过程中出现了异常,会被捕获,并且在 processDispatchResult 方法中统一处理,该方法还会进行视图的渲染:

https://i-blog.csdnimg.cn/direct/61e1c27ee9c04ff29829d051cfc60de5.png processDispatchResult 方法的整体逻辑:

// DispatcherServlet 中用于处理处理器执行结果的方法(无论是正常返回 ModelAndView 还是发生异常)
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
        @Nullable Exception exception) throws Exception {

    // 标记是否进入了异常处理视图(比如跳转到 error.jsp 等)
    boolean errorView = false;

    // 如果 Controller 执行过程中发生了异常
    if (exception != null) {
        // 如果是特殊的 ModelAndViewDefiningException,说明异常中直接包含了一个要跳转的视图
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView(); // 从异常中取出 ModelAndView
        }
        else {
            // 否则进入标准异常处理流程,由异常解析器(HandlerExceptionResolver)来处理异常,返回 ModelAndView
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception); // 调用异常处理器链
            errorView = (mv != null); // 如果异常处理器返回了视图,标记 errorView = true
        }
    }

    // 如果最终拿到了一个 ModelAndView 且没有被清空(wasCleared 表示是否显式清除视图渲染流程)
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response); // 执行视图渲染流程(包括视图名解析、模板引擎处理、输出 HTML)
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request); // 如果是错误页面,清除 request 中设置的错误信息
        }
    }
    else {
        // 如果 ModelAndView 是 null 或者已被清除,说明 controller 自行处理了 response,不需要视图渲染
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned."); 
        }
    }

    // 如果当前请求是异步处理(比如 @Async 或 DeferredResult 等),则跳过后续操作
    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // 在 forward 场景下开启异步处理了,直接 return,不做后续 cleanup
        return;
    }

    // 如果存在 handler,则触发 afterCompletion 回调(对应 HandlerInterceptor 的 afterCompletion 方法)
    // 这是请求生命周期的最后一步,无论是否异常都要执行
    if (mappedHandler != null) {
        // 这里不传异常,表示异常已经处理过(前面已经调用过 processHandlerException)
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}