SpringMVC启动与请求处理流程解析
目录
SpringMVC的基本结构
1.MVC简介
2.基本结构
什么是Handler?
什么是HandlerMapping?
什么是HandlerAdapter?
@RequestMapping方法参数解析
DispatcherServlet的init()方法
DispatcherServlet的doService()方法
SpringBoot整合SpringMVC
SpringMVC执行流程图
SpringMVC的基本结构
1.MVC简介
以前的纯Servlet的处理方式:
@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String type = req.getParameter(Constant.REQUEST_PARAMETER_TYPE);if(type != null && !"".equals(type)){if(Constant.SERVLET_TYPE_SAVE.equals(type)){// 添加用户信息try {saveOrUpdateUser(req, resp);} catch (Exception e) {e.printStackTrace();}}else if(Constant.SERVLET_TYPE_UPDATE.equals(type)){// 更新用户信息}else if(Constant.SERVLET_TYPE_DELETE.equals(type)){// 删除用户信息deleteUserById(req, resp);}else if(Constant.SERVLET_TYPE_QUEYR.equals(type)){// 查询用户queryUser(req, resp);}else if(Constant.SERVLET_TYPE_QUERYBYID.equals(type)){// 查询单条记录String id = req.getParameter("id");User user = userService.queryById(Integer.parseInt(id));// 跳转到更新的页面同时保存数据到Request作用域中req.setAttribute("user",user);req.getRequestDispatcher("/user/userUpdate.jsp").forward(req,resp);}else if(Constant.SERVLET_TYPE_CHECK.equals(type)){// 验证账号是否存在String userName = req.getParameter("userName");String s = userService.checkUserName(userName);resp.getWriter().println(s);resp.flushBuffer();}}else{// 查询用户信息queryUser(req, resp);}}
为了尽量减少依赖Servlet API,提高程序的可测试性、可复用性而发展出了很多的框架技术:
-
Struts1
-
Struts2
-
SpringMVC
2.基本结构
然后我们来看看SpringMVC的基本结构
什么是Handler?
Handler表示请求处理器,在SpringMVC中有四种Handler:
- 实现了Controller接口的Bean对象
- 实现了HttpRequestHandler接口的Bean对象
- 添加了@RequestMapping注解的方法
- 一个HandlerFunction对象
比如实现了Controller接口的Bean对象:
@Component("/test")
public class MyController implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {System.out.println("hello");return new ModelAndView();}
}
实现了HttpRequestHandler接口的Bean对象:
@Component("/test")
public class MyController implements HttpRequestHandler {@Overridepublic void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("hello");}
}
添加了@RequestMapping注解的方法:
@RequestMapping
@Component
public class MyController {@Autowiredprivate MyService myService;@RequestMapping(method = RequestMethod.GET, path = "/test")@ResponseBodypublic String test(String username) {return "hello";}}
一个HandlerFunction对象(以下代码中有两个):
@Configuration
public class AppConfig {@Beanpublic RouterFunction<ServerResponse> person() {return route().GET("/app/person", request -> ServerResponse.status(HttpStatus.OK).body("Hello GET")).POST("/app/person", request -> ServerResponse.status(HttpStatus.OK).body("Hello POST")).build();}
}
什么是HandlerMapping?
HandlerMapping负责去寻找Handler,并且保存路径和Handler之间的映射关系。
因为有不同类型的Handler,所以在SpringMVC中会由不同的HandlerMapping来负责寻找Handler,比如:
- BeanNameUrlHandlerMapping:负责Controller接口和HttpRequestHandler接口
- RequestMappingHandlerMapping:负责@RequestMapping的方法
- RouterFunctionMapping:负责RouterFunction以及其中的HandlerFunction
BeanNameUrlHandlerMapping的寻找流程:
- 找出Spring容器中所有的beanName
- 判断beanName是不是以“/”开头
- 如果是,则把它当作一个Handler,并把beanName作为key,bean对象作为value存入handlerMap中
- handlerMap就是一个Map
RequestMappingHandlerMapping的寻找流程:
RequestMappingHandlerMapping是在初始化方法afterPropertiesSet()中寻找的
- 找出Spring容器中所有beanType
- 判断beanType是不是有@Controller注解,或者是不是有@RequestMapping注解
- 判断成功则继续找beanType中加了@RequestMapping的Method
- 并解析@RequestMapping中的内容,比如method、path,封装为一个RequestMappingInfo对象
- 把path作为key,RequestMappingInfo作为value存入pathLookup中
- 把RequestMappingInfo对象做为key,Method对象封装为HandlerMethod对象后作为value,存入registry中
pathLookup和registry就是一个Map,这样我们就能通过path找到对应的处理方法
RouterFunctionMapping的寻找流程会有些区别,但是大体是差不多的,相当于是一个path对应一个HandlerFunction。
各个HandlerMapping除开负责寻找Handler并记录映射关系之外,自然还需要根据请求路径找到对应的Handler,在源码中这三个HandlerMapping有一个共同的父类AbstractHandlerMapping
AbstractHandlerMapping实现了HandlerMapping接口,并实现了getHandler(HttpServletRequest request)方法。
AbstractHandlerMapping会负责调用子类的getHandlerInternal(HttpServletRequest request)方法从而找到请求对应的Handler,寻找Handler的源码实现在各个HandlerMapping子类中的getHandlerInternal()中,根据请求路径找到Handler的过程并不复杂,因为路径和Handler的映射关系已经存在Map中了。
比较困难的点在于,当DispatcherServlet接收到一个请求时,该利用哪个HandlerMapping来寻找Handler呢?
看源码:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;
}
很简单,就是遍历,找到就返回,默认顺序为:
什么是HandlerAdapter?
找到了Handler之后,接下来就该去执行了,但是由于有不同种类的Handler,所以执行方式是不一样的,再来总结一下Handler的类型:
- 实现了Controller接口的Bean对象,执行的是Bean对象中的handleRequest()
- 实现了HttpRequestHandler接口的Bean对象,执行的是Bean对象中的handleRequest()
- 添加了@RequestMapping注解的方法,具体为一个HandlerMethod,执行的就是当前加了注解的方法
- 一个HandlerFunction对象,执行的是HandlerFunction对象中的handle()
所以,按逻辑来说,找到Handler之后,我们得判断它的类型,比如代码可能是这样的:
Object handler = mappedHandler.getHandler();
if (handler instanceof Controller) {((Controller)handler).handleRequest(request, response);
} else if (handler instanceof HttpRequestHandler) {((HttpRequestHandler)handler).handleRequest(request, response);
} else if (handler instanceof HandlerMethod) {((HandlerMethod)handler).getMethod().invoke(...);
} else if (handler instanceof HandlerFunction) {((HandlerFunction)handler).handle(...);
}
但是SpringMVC并不是这么写的,还是采用的适配模式,把不同种类的Handler适配成一个HandlerAdapter,后续再执行HandlerAdapter的handle()方法就能执行不同种类Hanlder对应的方法。
针对不同的Handler,会有不同的适配器:
- HttpRequestHandlerAdapter
- SimpleControllerHandlerAdapter
- RequestMappingHandlerAdapter
- HandlerFunctionAdapter
适配逻辑为:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}}}throw new ServletException("No adapter for handler [" + handler +"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
传入handler,遍历上面四个Adapter,谁支持就返回谁,比如判断的代码依次为:
public boolean supports(Object handler) {return (handler instanceof HttpRequestHandler);
}public boolean supports(Object handler) {return (handler instanceof Controller);
}public final boolean supports(Object handler) {return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}public boolean supports(Object handler) {return handler instanceof HandlerFunction;
}
根据Handler适配出了对应的HandlerAdapter后,就执行具体HandlerAdapter对象的handle()方法了,比如:
HttpRequestHandlerAdapter的handle():
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {((HttpRequestHandler) handler).handleRequest(request, response);
SimpleControllerHandlerAdapter的handle():
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return ((Controller) handler).handleRequest(request, response);
}
HandlerFunctionAdapter的handle():
HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
serverResponse = handlerFunction.handle(serverRequest);
因为这三个接收的直接就是Requeset对象,不用SpringMVC做额外的解析,所以比较简单,比较复杂的是RequestMappingHandlerAdapter,它执行的是加了@RequestMapping的方法,而这种方法的写法可以是多种多样,SpringMVC需要根据方法的定义去解析Request对象,从请求中获取出对应的数据然后传递给方法,并执行。
@RequestMapping方法参数解析
当SpringMVC接收到请求,并找到了对应的Method之后,就要执行该方法了,不过在执行之前需要根据方法定义的参数信息,从请求中获取出对应的数据,然后将数据传给方法并执行。
一个HttpServletRequest通常有:
- request parameter
- request attribute
- request session
- reqeust header
- reqeust body
比如如下几个方法:
public String test(String username) {return "123";
}
表示要从request parameter中获取key为username的value
public String test(@RequestParam("uname") String username) {return "123";
}
表示要从request parameter中获取key为uname的value
public String test(@RequestAttribute String username) {return "123";
}
表示要从request attribute中获取key为username的value
public String test(@SessionAttribute String username) {return "123";
}
表示要从request session中获取key为username的value
public String test(@RequestHeader String username) {return "123";
}
表示要从request header中获取key为username的value
public String test(@RequestBody String username) {return "123";
}
表示获取整个请求体
所以,我们发现SpringMVC要去解析方法参数,看该参数到底是要获取请求中的哪些信息。
而这个过程,源码中是通过HandlerMethodArgumentResolver来实现的,比如:
- RequestParamMethodArgumentResolver:负责处理@RequestParam
- RequestHeaderMethodArgumentResolver:负责处理@RequestHeader
- SessionAttributeMethodArgumentResolver:负责处理@SessionAttribute
- RequestAttributeMethodArgumentResolver:负责处理@RequestAttribute
- RequestResponseBodyMethodProcessor:负责处理@RequestBody
- 还有很多其他的...
而在判断某个参数该由哪个HandlerMethodArgumentResolver处理时,也是很粗暴:
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;}
就是遍历所有的HandlerMethodArgumentResolver,哪个能支持处理当前这个参数就由哪个处理。
DispatcherServlet的init()方法
init()做了两件事情
-
完成了IOC的初始化
-
完成了SpringMVC核心组件的初始化
在这个方法里面主要会配置spring容器的父子容器,并且还会执行initStrategies()初始化映射器和适配器分别对应initHandlerMappings()和initHandlerAdapters()这两个方法,最后启动spring容器。
在这两个方法里都会先检查程序员自己有没有定义HandlerMapping.class或HandlerAdapter.class类型的bean,如果没有的话就会走springmvc的默认策略getDefaultStrategies()去DispatcherServlet.properties文件中加载Bean,默认HandlerMapping有3个Bean,HandlerAdapter有4个Bean。
DispatcherServlet的doService()方法
tomcat会处理请求,然后调用service()方法,service方法调用DispatcherServlet的doService()方法,doService()调用doDispatch(),也就是一套模板方法。
service方法是在用户请求到来的时候触发的。也就是具体处理请求的方法。我们来看下,直接进入到doDispatch方法中
核心流程如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 检查是否是multipart请求processedRequest = checkMultipart(request);// 进行映射 找到对应的handlermappedHandler = getHandler(processedRequest);if (mappedHandler == null) {//404noHandlerFound(processedRequest, response);return;}// 找到最合适的HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 前置拦截器if (!mappedHandler.applyPreHandle(processedRequest, response)) {// 返回false就不进行后续处理了return;}// 真正执行handlemv = ha.handle(processedRequest, response, mappedHandler.getHandler());//后置拦截器mappedHandler.applyPostHandle(processedRequest, response, mv);// 渲染视图processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
SpringBoot整合SpringMVC
然后我们来看下载SpringBoot项目中是怎么自动装配SpringMVC框架的,首先我们找到对应的配置类
同时我们也需要关注下这个配置类
在这个配置类中注入的HandlerMapping和HandlerAdapter的具体实现类
同时也注入了DispatcherServlet
SpringMVC执行流程图
spring MVC
相关文章:

SpringMVC启动与请求处理流程解析
目录 SpringMVC的基本结构 1.MVC简介 2.基本结构 什么是Handler? 什么是HandlerMapping? 什么是HandlerAdapter? RequestMapping方法参数解析 DispatcherServlet的init()方法 DispatcherServlet的doService()方法 SpringBoot整合SpringMVC …...

C++ 日志库 spdlog 使用教程
Spdlog是一个快速、异步、线程安全的C日志库,他可以方便地记录应用程序的运行状态,并提供多种输出格式。官网:https://github.com/gabime/spdlog 安装教程可以参考:https://blog.csdn.net/Harrytsz/article/details/144887297 S…...
`http_port_t
http_port_t 是 SELinux(Security-Enhanced Linux)中的一种端口类型标签,用于标识哪些端口可以被 HTTP 和 HTTPS 服务使用。SELinux 是一种强制访问控制(MAC)安全模块,它通过定义安全策略来限制进程对系统资…...

SpringBoot中实现拦截器和过滤器
【SpringBoot中实现过滤器和拦截器】 1.过滤器和拦截器简述 过滤器Filter和拦截器Interceptor,在功能方面很类似,但在具体实现方面差距还是比较大的。 2.过滤器的配置 2.1 自定义过滤器,实现Filter接口(SpringBoot 3.0 开始,jak…...
不锈钢均温板结合强力粘合技术革新手机内部架构
摘要: 本文介绍了一种创新性的手机内部架构设计方案,其中不锈钢均温板不仅作为高效的散热元件,还充当了手机中框的主要结构件。通过使用强力不可拆胶水将主板、尾插和其他关键部件直接粘合到均温板上,该方案实现了更为紧密的热耦合…...

Docker安装使用
文章目录 Docker安装Docker的基础使用搜索&拉取镜像 Docker的生命周期利用Docker切换不同OSDocker容器 镜像的保存&分享Docker存储Docker网络 Docker安装 更新apt索引 sudo apt-get update添加Docker所需要的依赖 apt-get install ca-certificates curl gnupg lsb-r…...
React 如何进行路由变化监听
一、使用react-router库(以react-router-dom为例) 1. 历史(history)对象监听 1.1 原理 react-router内部使用history对象来管理路由历史记录。可以通过访问history对象来监听路由变化。在基于类的组件中,可以通过组…...

Unity UGUI使用技巧与经验总结(不定期更新)
Text自动缩放参考连接: Unity -UGUI中Text文本框的自动调整,字体大小的自适应调节_unity添加的字体大小锁定-CSDN博客 Toggle按钮选择时,显示对应的UI界面: 为Toggle组件的On Value Change事件添加对需要显示的对象的SetActive…...

中国乡镇界shp全境arcgis格式shp数据乡镇名称下载后内容测评
下载乡镇界shp链接:https://download.csdn.net/download/zhongguonanren99/19354855 标题中的“中国乡镇界shp全境arcgis格式shp数据乡镇名称2012年”揭示了这个数据集的核心内容。它是一个地理信息系统(GIS)数据,具体来说是使用…...

第 31 章 - 源码篇 - Elasticsearch 写入流程深入分析
写入源码分析 接收与处理 请求首先会被 Netty4HttpServerTransport 接收,接着交由 RestController 进行路由分发。 private void tryAllHandlers(final RestRequest request, final RestChannel channel, final ThreadContext threadContext) throws Exception {…...

node.js下载、安装、设置国内镜像源(永久)(Windows11)
目录 node-v20.18.0-x64 工具下载安装设置国内镜像源(永久) node-v20.18.0-x64 工具 系统:Windows 11 下载 官网https://nodejs.org/zh-cn/download/package-manager 版本我是跟着老师选的node-v20.18.0-x64如图选择 Windows、x64、v2…...
小于n的最大数 - 贪心算法 - C++
字节经典面试题 给定一个整数n,并从1~9中给定若干个可以使用的数字,根据上述两个条件,得到每一位都为给定可使用数字的、最大的小于整数n的数,例如,给定可以使用的数字为 {2,3,8} 三个数:给定 n3589&#x…...

【顶刊TPAMI 2025】多头编码(MHE)之极限分类 Part 3:算法实现
目录 1 三种多头编码(MHE)实现1.1 多头乘积(MHP)1.2 多头级联(MHC)1.3 多头采样(MHS)1.4 标签分解策略 论文:Multi-Head Encoding for Extreme Label Classification 作者…...
解决CentOS 8 YUM源更新后报错问题:无法下载AppStream仓库元数据
背景介绍 在尝试更新CentOS 8的YUM源以使用阿里云镜像时,遇到了Failed to download metadata for repo appstream的错误。此错误通常出现在执行yum clean all && yum makecache命令之后,表明系统无法从指定的URL获取AppStream仓库的元数据。本文…...
[python3]Excel解析库-openpyxl
https://openpyxl.readthedocs.io/en/stable/ openpyxl 是一个用于读写 Excel 2010 xlsx/xlsm/xltx/xltm 文件的 Python 库。它允许开发者创建、修改和保存电子表格,而无需依赖 Microsoft Excel 软件本身。openpyxl 支持读取和写入 Excel 的工作簿(Work…...

Docker 远程访问完整配置教程以及核心参数理解
Docker 远程访问完整配置教程 以下是配置 Docker 支持远程访问的完整教程,包括参数说明、配置修改、云服务器安全组设置、主机防火墙配置,以及验证远程访问的详细步骤。 1. 理解 -H fd:// 参数的作用(理解了以后容易理解后面的操作ÿ…...

王老吉药业SRM系统上线 携手隆道共启战略合作新篇章
12月27日,广州王老吉药业股份有限公司(简称“王老吉药业”)SRM项目上线启动会,在王老吉科普教育基地——“吉园”隆重举行。广药集团纪委主任陈耕、王老吉药业总工程师黄晓丹、隆道公司总裁吴树贵、项目经理赵耀、供应商代表郭伟及…...
MyBatis 配置文件全解析
一、MyBatis 配置文件为何至关重要? 在 Java 后端开发领域,MyBatis 作为一款广受欢迎的持久层框架,极大地简化了数据库操作。而 MyBatis 配置文件,恰似整个框架的 “神经中枢”,掌控着其运行的方方面面,对…...

unity学习6:unity的3D项目的基本界面和菜单
目录 1 unity界面的基本认识 1.1 file 文件 1.2 edit 编辑/操作 1.3 Assets 1.4 gameobject 游戏对象 1.5 组件 1.6 windows 2 这些部分之间的关系 2.1 关联1: Assets & Project 2.2 关联2:gameobject & component 2.3 关联3…...

企业二要素如何用C#实现
一、什么是企业二要素? 企业二要素,通过输入统一社会信用代码、企业名称或统一社会信用代码、法人名称,验证两者是否匹配一致。 二、企业二要素适用哪些场景? 例如:信用与金融领域 1.信用评级:信用评级…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...