spring5源码篇(12)——spring-mvc请求流程

spring-framework 版本:v5.3.19

文章目录

  • 一、请求流程
    • 1、处理器映射器
      • 1.1、 RequestMappingHandlerMapping
      • 1.2、获取对应的映射方法
      • 1.3、添加拦截器
    • 2、获取合适的处理器适配器
    • 3、通过处理器适配器执行处理器方法
      • 3.1、拦截器的前置后置
      • 3.2、处理器的执行
        • 3.2.1 参数解析器解析参数和执行处理器方法
        • 3.2.2 结果处理器处理结果
        • 3.2.3 消息转化器
    • 4、视图解析器
  • 二、请求流程中的一些问题
  • 1、各种组件如处理器映射器,处理器适配器,视图解析器等是如何注入到spring容器的?
  • 2、RequestMappingInfoHandlerMapping 处理器映射器中维护的映射map (即MapingRegister) 是如何初始化的?
  • 3、处理器映射器中的拦截器是如何添加的?
  • 4、处理器适配器的参数解析器和结果处理器是如何来的?
  • 5、参数解析器/结果处理器中的消息转化器是如何来的?

一、请求流程

总体流程在 DispatchServelt#doDispatch 方法
在这里插入图片描述

1、处理器映射器

首先会获取根据url去映射对应的处理器(即接口执行方法)
看到对应的 getHandler 方法
在这里插入图片描述
为方便阅读,进入debug。可以看到springmvc默认为我们注册了三个handlerMapping。

springMvc中的各个组件如处理器映射器,处理器适配器,视图解析器等是如何注入到容器的,先暂时不看,第二部分在看(问题1)。

1.1、 RequestMappingHandlerMapping

而其中我们最常用的一个就是RequestMappingHandlerMapping,所以这里只看这个类的 getHandler 方法(其实也是其抽象父类的方法)。
在这里插入图片描述

1.2、获取对应的映射方法

getHandlerInternal 方法入手,看看是如何把一个请求映射到对应的方法的。
在这里插入图片描述
这里首先会从 mappingRegister 中去找可以根据url直接得到的映射(即没有替换符),如果找不到再去所有注册的映射中找,然后返回一个最匹配的映射对应的处理器。

mappingRegister 可以简单的理解成 Map<url,handler>。实际上,为提供效率一共维护了3个map
在这里插入图片描述
这些map是如何初始化的,第二部分再讲(问题2)。

至于是如何匹配这些映射的,从 addMatchingMappings 入手
在这里插入图片描述
这里其实就是把匹配逻辑抽象成一个个condition,如果满足所有 condition 说明这是一个可用的处理器。注意,仅是可用而已,并不一定会用这个处理器。因为可能同时会匹配到多个可用的处理器,但是最终只会返回最匹配的那一个。

1.3、添加拦截器

在前面的总体流程中注释到,获取处理器的时候,返回的是一个处理器链。因为可能会配置有拦截器,而拦截器的添加从 getHandlerExecutionChain 入手。
在这里插入图片描述
可以看到,就只是遍历当前 handlerMapping 所添加的所有拦截器,若匹配,则将该拦截器添加到处理器链中而已。至于 handlerMapping 的拦截器是如何添加的,第二部分再讲(问题3)。

2、获取合适的处理器适配器

在这里插入图片描述
Spring MVC可能会有多种类型的处理器,例如控制器(Controller)、RESTful控制器等。处理器适配器负责选择并调用合适的处理器来处理请求。这一步就是去获取一个合适的处理器适配器,其实就是一个简单遍历获取,在spring mvc 默认提供的几种适配器中,最常用的还是 RequestMappingHandlerAdapter

3、通过处理器适配器执行处理器方法

3.1、拦截器的前置后置

在正式看处理器之前,顺便提一嘴拦截器的执行。
在执行实际业务方法之前会先执行处理器链中拦截器前置方法。同理,执行完业务方法后会执行处理器链中拦截器的后置方法。
在这里插入图片描述
拦截器的执行就是一个遍历,就不上代码了。

3.2、处理器的执行

ha.handle 入手
在这里插入图片描述
不管是哪种适配器,最终都会依次通过 参数解析器、处理器、结果处理器 将方法返回值封装成 ModelAndView 对象。

3.2.1 参数解析器解析参数和执行处理器方法

从 invokeForRequest 入手
在这里插入图片描述
处理器方法的执行就是反射实现的,没啥好看的。主要看参数解析器是如何解析参数的。
在这里插入图片描述
InvocableHandlerMethod 中维护了名为 resolvers 的 HandlerMethodArgumentResolverComposite 对象(多个参数解析器的封装),并且这个变量的值是处理器适配器传过来的。至于处理器适配器的参数解析器是哪来的,先暂时不看,后面第二部分再看(问题4)。这里只需知道参数解析器是在这里解析的以及值是处理器适配器传过来的就可以了。

3.2.2 结果处理器处理结果

从 returnValueHandlers.handleReturnValue 入手
在这里插入图片描述
**跟参数解析器维护的复合对象一样,也维护了一个名为 returnValueHandlers 的 HandlerMethodReturnValueHandlerComposite 复合对象,并且这个变量的值也是处理器适配器传过来的。**至于处理器适配器的结果处理器是哪来的,也先暂时不看。

一般来说参数解析器的命名为 xxxMethodArgumentResolver 而结果处理器的命名为 xxxMethodReturnValueHandler。但是如果即是参数解析器又是结果处理器的命名就会xxxMethodProcessor。而我们常用的 RequestResponseBodyMethodProcessor 就是这种类型。
在这里插入图片描述

3.2.3 消息转化器

首先并不是所有的参数解析器或者结果处理器都会用到消息转化器。,但至少我们最常用的 **RequestResponseBodyMethodProcessor 无论是解析参数还是处理结果,都会用到消息转化器。**对应的方法分别是 readWithMessageConverters,writeWithMessageConverters。并且write是直接写到 http 的response,这也意味着 @ResponseBody 不需要通过视图解析器解析渲染视图。
在这里插入图片描述

在 RequestResponseBodyMethodProcessor 中无论是read还是write,都会去遍历参数解析器/结果处理器中维护的消息转化器列表。至于 参数解析器/结果处理器的消息转化器列表是哪里来的,也先暂时不看,后面在看(问题5)。

4、视图解析器

试想这么一个接口,没有 @ResponseBody 并且返回值是一个字符串。

如果有 @ResponseBody 注解,ModelAndView为null,无需解析渲染视图。
在这里插入图片描述
在我们配置如下的视图解析器时会访问到servlet容器下的mv.html页面。
在这里插入图片描述
从字符串到返回的具体页面,这正是视图解析器所做的事情。

关于视图解析器,从 processDispatchResult 入手
在这里插入图片描述
解析:如果 ModelAndView 有视图名(在上面的例子中视图名就是方法返回的字符串)就会调用视图解析器去解析视图名得到一个视图 View 对象,反之就从 ModelAndView 中直接获取 View 对象。总之,不管怎样,这里一定要有 View 对象,没有就报错。
渲染:然后再调用上一步得到的 View 对象 render 方法并将 ModelAndView 里的 Model 数据传入,从而将数据渲染带视图上(如:jsp)。最后将渲染后的结果返回至前端。


至此一个完整的请求流程就看完了。为更形象记忆 spring mvc 各组件之间的关系,这里附上一张网图
在这里插入图片描述

二、请求流程中的一些问题

1、各种组件如处理器映射器,处理器适配器,视图解析器等是如何注入到spring容器的?

答:@EnableWebMvc注解(相当于xml配置: <mvc:annotation-driven/>)。
在这里插入图片描述
@EnableWebMvc 引入了 DelegatingWebMvcConfiguration,而这个类中就默认注入了 spring mvc 的各种组件。
比如:
在这里插入图片描述

2、RequestMappingInfoHandlerMapping 处理器映射器中维护的映射map (即MapingRegister) 是如何初始化的?

答:在 RequestMappingInfoHandlerMapping bean生命周期的 afterPropertiesSet。
在这里插入图片描述

3、处理器映射器中的拦截器是如何添加的?

答:以 RequestMappingInfoHandlerMapping 为例,在 DelegatingWebMvcConfiguration 注入处理器映射器的时就同时会去set拦截器。
在这里插入图片描述
其中 getInterceptors 代码如下
在这里插入图片描述
说白了就是 getInterceptors 方法,会依次调用所有的 WebMvcConfigurer.addInteceptors 方法将自定义配置的拦截器添加到处理器映射器中的 InterceptorRegistry。之后处理器映射器在映射处理器时,将其中匹配的拦截器添加到处理链,进而实现拦截器的效果。

正是因为自动注入的原理,所以我们平时配置spring mvc时只需注入一个实现 WebMvcConfigurer 接口的类。

但其实截至目前为止添加的还只是一个原始版本的拦截器,真正使用的拦截器是适配后的版本。
适配的时机是在处理器映射器生命周期的 setApplicationContext 方法。(最后会调用到 initApplicationContext)
在这里插入图片描述

4、处理器适配器的参数解析器和结果处理器是如何来的?

答:在 RequestMappingHandlerAdapter bean生命周期的 afterPropertiesSet。

在这里插入图片描述
具体添加了哪些参数解析器,结果处理器就不细看了,太多了…

5、参数解析器/结果处理器中的消息转化器是如何来的?

答:来自处理器适配器,而处理器适配器的消息转化器来自 WebMvcConfigurer 配置或者默认配置。

如下图:
(参数解析器/结果处理器中的消息转化器来自处理器适配器)
在这里插入图片描述
(适配器的来自 WebMvcConfigurer 配置或者默认配置)
在这里插入图片描述

至于this.configurers.configureMessageConverters(converters);
首先configurers在前面第三个问题已经看过了,就是自动注入的 WebMvcConfigurer 总和。所以这句总的来说就是依次调用所有的 WebMvcConfigurer.configureMessageConverters 方法将自定义配置的消息添加到处理器适配器中。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

重生之我要学C++第四天

这篇文章的主要内容是类的默认成员函数。如果对大家有用的话&#xff0c;希望大家三连支持&#xff0c;博主会继续努力&#xff01; 目录 一.类的默认成员函数 二.构造函数 三.析构函数 四.拷贝构造函数 五.运算符重载 一.类的默认成员函数 如果一个类中什么成员都没有&…

目标检测-击穿黑夜的PE-YOLO

前言 当前的目标检测模型在许多基准数据集上取得了良好的结果&#xff0c;但在暗光条件下检测目标仍然是一个巨大的挑战。为了解决这个问题&#xff0c;作者提出了金字塔增强网络&#xff08;PENet&#xff09;并将其与YOLOv3结合&#xff0c;构建了一个名为PE-YOLO的暗光目标检…

Linux中的ldd命令使用方法总结

ldd&#xff08;List Dynamic Dependencies&#xff09;命令是Linux系统中的一个工具 它用于打印出一个可执行文件所依赖的共享库文件&#xff08;动态链接库&#xff09; 当你运行ldd命令&#xff0c;并跟上一个可执行文件作为参数&#xff0c;它会列出该可执行文件所需要的…

【Spring】Spring 总览

一、简单介绍一下 Spring Spring是一个全面的、企业应用开发的一站式解决方案&#xff0c;贯穿表现层、业务层、持久层&#xff0c;可以轻松和其他框架整合&#xff0c;具有轻量级、控制反转、面向切面、容器等特征。 轻量级 &#xff1a; 空间开销和时间开销都很轻量 控制反…

栈和队列第二弹,完结篇

&#x1f49b;1.队列的基本底层实现 public class MyQueue {int array[];int usedsize0;public MyQueue(){this.arraynew int [5];} &#x1f499;2.判断是否满&#xff0c;满了需要扩容 Arrays.copyOf(数组&#xff0c;数组的长度&#xff09;&#xff1b;我常常会忘记哈…

Java版本企业工程项目管理系统平台源码(三控:进度组织、质量安全、预算资金成本、二平台:招采、设计管理)

工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#…

Safari 查看 http 请求

文章目录 1、开启 Safari 开发菜单2、显示 JavaScript 控制台 1、开启 Safari 开发菜单 Safari 设置中&#xff0c;打开开发菜单选项 *** 选择完成后&#xff0c;Safari 的目录栏就会出现一个 开发 功能。 2、显示 JavaScript 控制台 开启页面后&#xff0c;在开发中选中 显…

掌握Python的X篇_10+11_if分支语句、else语句、elif语句

文章目录 1. if关键字及语法2. 语句块的概念3. else语句4. elif语句 1. if关键字及语法 基本语法如下&#xff1a; if 条件表达式:条件为True时&#xff0c;要执行的语句举例&#xff1a; number int(input("Input an number")) if number > 5 :print("这…

F12 浏览器调试模式页面刷新 network 日志刷新消失的解决办法

每次请求刷新后都把之前的请求记录刷新掉了&#xff0c;把preserve log勾选上后&#xff0c;所有的请求都会保留&#xff0c;再也不怕抓不到记录了。

SpringBoot项目部署在Windows与Centos上

文章目录 Windows部署一、github上下载文件winsw二、文件目录三、编辑xml文件四、安装服务五、启动服务六、把jar包放到项目外面七、添加限制内存 Linux部署一、准备二、服务三、操作 Windows部署 windows部署服务借鉴于此篇博文 一、github上下载文件winsw 点击链接下载下图…

windows切换php版本以及composer

前提 安装php8.2 安装Php7.4 下载 nts是非线程安全的&#xff0c;这里选择线程安全的&#xff0c;选择64位 解压缩 修改系统环境变量 修改为php-7的 cmd中输入php -v查看 找到composer存放路径C:\ProgramData\ComposerSetup\bin 将三个文件复制到php目录下 重启电脑…

【云原生】Docker容器命令监控+Prometheus监控平台

目录 1.常用命令监控 docker ps docker top docker stats 2.weave scope 1.下载 2.安装 3.访问查询即可 3.Prometheus监控平台 1.部署数据收集器cadvisor 2.部署Prometheus 3.部署可视化平台Gragana 4.进入后台控制台 1.常用命令监控 docker ps [rootlocalhost ~…

GitHub Copilot:让开发编程变得像说话一样简单

引用&#xff1a; 人类天生就梦想、创造、创新。但今天&#xff0c;我们花太多时间被繁重的工作所消耗&#xff0c;花在消耗我们时间、创造力和精力的任务上。为了重新连接我们工作的灵魂&#xff0c;我们不仅需要一种更好的方式来做同样的事情&#xff0c;更需要一种全新的工…

Linux下CMake开发

CMake编译和运行C文件 编写CMakeLists.txt # 声明要求的 cmake 最低版本 cmake_minimum_required( VERSION 3.1 )# 声明一个 cmake 工程 project( pro )# 设置编译模式 set( CMAKE_BUILD_TYPE "Release" )#添加OPENCV库 #指定OpenCV版本&#xff0c;代码如下 #find…

笔记20230727

1. http2.0&#xff0c;概念就不说了&#xff0c;查看是否使用&#xff1a;network调试&#xff0c;查看请求的header-view source&#xff0c;可以查看http版本&#xff1b;后端&#xff0c;如nginx&#xff0c;配置&#xff0c;http2表示开启。后端开启、浏览器支持&#xff…

PHP8的注释-PHP8知识详解

欢迎你来到PHP服务网&#xff0c;学习《PHP8知识详解》系列教程&#xff0c;本文学习的是《PHP8的注释》。 什么是注释&#xff1f; 注释是在程序代码中添加的文本&#xff0c;用于解释和说明代码的功能、逻辑或其他相关信息。注释通常不会被编译器或解释器处理&#xff0c;而…

《TCP IP网络编程》第十一章

第 11 章 进程间通信 11.1 进程间通信的基本概念 通过管道实现进程间通信&#xff1a; 进程间通信&#xff0c;意味着两个不同的进程中可以交换数据。下图是基于管道&#xff08;PIPE&#xff09;的进程间通信的模型&#xff1a; 可以看出&#xff0c;为了完成进程间通信&…

Redis 笔记,基本数据类型、持久化、主从、集群等等问题

标题 &#x1f600;&#x1f600;&#x1f600;创作不易&#xff0c;各位看官点赞收藏. 文章目录 标题Redis 基础笔记1、安装及环境搭建2、Redis 数据类型2.1、String2.2、List2.3、Hash2.4、Set2.5、Zset2.6、BitMap2.7、HyperLogLog2.8、Geospatial2.9、Stream 3、Redis 持久…

C++之普通函数指针/类成员函数指针/lambda回调函数总结(一百六十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Go 语言入门指南:基础语法和常用特性解析

文章目录 Hello,World变量、指针及赋值变量和常量指针赋值 选择和循环选择循环 基本数据类型整型整型的取值范围 运算符二元运算符一元运算符 浮点型复数和布尔类型 字符串runeUnicode和UTF-8按字节访问按字符rune访问特点 数组数组的定义1. 使用默认初始值2. 定义并初始化3. 省…