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

文章目录


前言

在Spring MVC中,请求处理的关键是DispatcherServletdoDispatch方法:
  其中包含了@RequestBody,@ResponseBody等注解的解析,参数的适配,方法拦截器的执行,目标方法的执行,返回值处理,以及异常处理等逻辑。

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

客户端请求

DispatcherServlet

getHandler(request)

HandlerExecutionChain(包含Handler和拦截器)

调用拦截器 preHandle()

调用 Controller 方法

调用拦截器 postHandle()

返回视图、渲染

调用拦截器 afterCompletion()

一、doDispatch

1.1、检查文件上传

当某个请求到达doDispatch时,首先会检查其是否为文件格式
  假设我在controller层中进行文件上传的请求处理:

java 复制代码
@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中配置:
  还需要在web.xml所引用的spring.xml中进行配置:
  这样在进入checkMultipart方法时,首先会利用multipartResolver校验该请求的类型是否为文件上传:
  调用的是StandardServletMultipartResolverisMultipart,在该方法中,首先会获取请求头中的 Content-Type,如果用户在表单中使用 <form enctype="multipart/form-data">,那么 Content-Type 就是:multipart/form-data;然后会忽略大小写判断 Content-Type 是否以 multipart/form-data 开头。  如果满足条件,则会进入multipartResolverresolveMultipart方法进行解析:
  调用的同样是StandardServletMultipartResolverresolveMultipart,关键代码,区分普通的form-data和文件类型的form-data:

  • 普通的form-data存入multipartParameterNames集合中。
  • 文件类型的form-data存入multipartFiles集合中。
      如果是文件请求,最后会封装成MultipartHttpServletRequest的对象,返回到方法的调用处:
      这时拿到的processedRequest的类型是MultipartHttpServletRequest,和最初记录的request类型不一致,故multipartRequestParsed 的值是true,标记本次请求为上传文件 (反之为false)

1.2、得到Handler

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

首先会遍历DispatcherServlet.properties中的三个默认HandlerMapping
  再去调用各自的getHandler方法,目前案例中是@Controller注解的模式,所以利用的是RequestMappingHandlerMapping,其中的关键代码,利用请求路径,去容器启动过程中收集的集合中找有无匹配的
  lookupHandlerMethod,会去pathLookup 集合中找,pathLookup:key存放了请求路径,value存放了注解的信息。
  最终找到对应的方法对象并返回:
  如果找不到匹配的,会在该方法中直接返回给浏览器404:
  getHandler方法的整体逻辑,除了上图中的根据路径找Handler,还有组装拦截器和跨域请求的逻辑:

java 复制代码
@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
  与案例中适配的应该是RequestMappingHandlerAdapter:
  这一块的代码和Spring AOP的切面适配一样,都是适配器模式的体现。

1.4、执行拦截器preHandle方法

在拿到Handler对应的适配器之后,就会去执行自定义拦截器的preHandle方法(如果有的话),preHandle方法条件不匹配,就直接结束,不会调用目标方法:
  注意如果有多个拦截器,preHandle的顺序是从前往后执行
  并且不满足preHandle定义的条件,还会去执行自定义拦截器链中重写的AfterCompletion方法**(顺序是从后往前执行)**。并且在执行的过程中抛出了异常,只会记录日志。

1.5、执行目标方法

如果拦截器链的preHandle方法条件满足,或者没有自定义拦截器,则会使用1.3中找到的适配器,调用其中的handle,执行目标方法:
  首先会初始化一个ModelAndView,然后进入关键代码invokeHandlerMethod
  执行目标方法的是在invokeHandlerMethodinvokeAndHandle:
  invokeAndHandle:
  invokeAndHandleinvokeForRequest:先获取请求参数,然后通过反射 调用目标方法:
  最后回到invokeAndHandle,设置返回状态码,并且使用返回值处理器对返回值进行处理:

1.6、执行拦截器PostHandle方法

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

1.7、处理异常&渲染视图

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

java 复制代码
// 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);
    }
}

相关推荐
一只叫煤球的猫7 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9657 小时前
tcp/ip 中的多路复用
后端
bobz9657 小时前
tls ingress 简单记录
后端
皮皮林5518 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友8 小时前
什么是OpenSSL
后端·安全·程序员
bobz9659 小时前
mcp 直接操作浏览器
后端
前端小张同学11 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook11 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康12 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在12 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net