当前位置: 首页 > article >正文

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

文章目录

  • 前言
  • 一、doDispatch
    • 1.1、检查文件上传
    • 1.2、得到Handler
    • 1.3、Handler适配
    • 1.4、执行拦截器preHandle方法
    • 1.5、执行目标方法
    • 1.6、执行拦截器PostHandle方法
    • 1.7、处理异常&渲染视图


前言

  在Spring MVC中,请求处理的关键是DispatcherServletdoDispatch方法:
在这里插入图片描述  其中包含了@RequestBody,@ResponseBody等注解的解析,参数的适配,方法拦截器的执行,目标方法的执行,返回值处理,以及异常处理等逻辑。
  Spring MVC整体请求流程,本篇只简单地进行主要流程的分析,诸如如何解析参数,解析返回值,渲染视图等不在此列。

客户端请求

DispatcherServlet

getHandler(request)

HandlerExecutionChain(包含Handler和拦截器)

调用拦截器 preHandle()

调用 Controller 方法

调用拦截器 postHandle()

返回视图、渲染

调用拦截器 afterCompletion()

一、doDispatch

1.1、检查文件上传

  当某个请求到达doDispatch时,首先会检查其是否为文件格式
在这里插入图片描述  假设我在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中配置:
在这里插入图片描述  还需要在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,还有组装拦截器和跨域请求的逻辑:

@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方法的整体逻辑:

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



http://www.kler.cn/a/589208.html

相关文章:

  • 从过拟合到强化学习:机器学习核心知识全解析
  • R 语言科研绘图 --- 密度图-汇总
  • C/C++基数排序(Radix Sort) 的排序算法。
  • 深入理解TCP/IP网络模型及Linux网络管理
  • Solidity基础 -- 哈希算法
  • C++ QT零基础教学(二)
  • tomato靶场通关攻略
  • Leetcode-131.Palindrome Partitioning [C++][Java]
  • ST的全新STM32U3微控制器(MCU)简析
  • 视频推拉流EasyDSS案例分析:互联网直播/点播技术与平台创新应用
  • 【PyTorch教学】pytorch 基本语法
  • 消息队列 Kafka、RocketMQ、RabbitMQ 对比与分析
  • 蓝桥杯备考:图论之Prim算法
  • 分类操作-06.根据id删除分类
  • python 打印阳历对应的日农历时间
  • .gitignore 文件用于 Git 应忽略的文件夹的格式
  • SNX币合规交易突破 XBIT去中心化交易所引领DEX安全新范式
  • Notepad++插件:快捷选择成对括号之间的内容
  • 【SpringMVC】常用注解:@RequestHeader
  • WEB UI自动化测试中,元素定位的八大定位方式详解