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);
    }
}

相关推荐
小杨40417 分钟前
springboot框架项目应用实践六(参数校验validation基础)
spring boot·后端·架构
java技术小馆44 分钟前
责任链模式如何减少模块之间的耦合
java·数据库·设计模式·责任链模式
大溪地C1 小时前
Spring Boot3整合Knife4j(4.5.0)
java·数据库·spring boot
Java&Develop1 小时前
java项目springboot 项目启动不了解决方案
java·开发语言·spring boot
一条闲鱼_mytube1 小时前
golang recover错误
开发语言·后端·golang
Asthenia04121 小时前
Java线程池怎么做预热?从硬编码到pool.prestartCoreThread
后端
shengjk12 小时前
只会写代码的程序员,注定没有出路!
人工智能·后端
飞奔的马里奥2 小时前
30天学习Java第四天——JVM规范
java·jvm·学习
shengjk12 小时前
Flink 中RocksDB 为什么将每个键和值的限制为 2^31 字节
人工智能·后端
₁ ₀ ₂ ₄2 小时前
深入理解 Reactor Netty 线程配置及启动命令设置
java·开发语言