Spring MVC源码分析のinit流程
文章目录
- 前言
- 一、 init
- 1.1、createWebApplicationContext
- 1.2、onRefresh
- 二、请求处理器
- 2.1、@RequestMapping
- 2.2、Controller接口
- 2.3、HttpRequestHandler接口
- 2.4、HandlerFunction
- 三、initHandlerMappings
- 3.1、getDefaultStrategies
- 3.1.1、RequestMappingHandlerMapping
- 3.1.2、BeanNameUrlHandlerMapping
- 四、initHandlerAdapters
- 总结
前言
本篇介绍Spring MVC 的初始化流程,源码的体现是HttpServletBean的init方法。通常Spring MVC的项目,需要打成war包,部署在Tomcat服务器上,也就是Spring MVC通常需要配合Tomcat使用,当然也可以使用Jetty、Undertow 等。
Spring MVC的源码,主要是分为两部分,第一部分是Tomcat启动时,Spring MVC上下文和容器的初始化。第二部分则是请求到达Tomcat,进行路径映射,转发到DispatcherServlet
然后进行处理并且返回的流程。
一、 init
在Tomcat容器启动时,会进入到HttpServletBean
的init方法
,在其中进行初始化的操作:
initServletBean
最终会跳转到FrameworkServlet
的initWebApplicationContext
方法,而在该方法中,最关键的代码是createWebApplicationContext
。
在执行完
initWebApplicationContext
方法刷新容器之后,会将容器对象赋值给webApplicationContext
属性,并且执行initFrameworkServlet
方法(是一个空实现)。
1.1、createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {//首先获取上下文,这里获取到的是XmlWebApplicationContext类型,即解析xml文件的模式Class<?> contextClass = getContextClass();if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + getServletName() +"': custom WebApplicationContext class [" + contextClass.getName() +"] is not of type ConfigurableWebApplicationContext");}//尝试通过类型去实例化 XmlWebApplicationContext 容器 //实例化完成后的XmlWebApplicationContext是空的,属性都是默认值ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);//设置环境wac.setEnvironment(getEnvironment());//设置父容器wac.setParent(parent);//拿到WEB-INF下的spring.xml配置文件String configLocation = getContextConfigLocation();if (configLocation != null) {wac.setConfigLocation(configLocation);}//完成后续容器初始化与刷新操作(配置、监听器、refresh 等)configureAndRefreshWebApplicationContext(wac);return wac;
}
configureAndRefreshWebApplicationContext
方法:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {// 如果当前容器的 id 还只是默认值(即内存地址),我们给它设置一个更有意义的 idif (ObjectUtils.identityToString(wac).equals(wac.getId())) {// 如果用户自定义设置了 contextId,则使用用户提供的if (this.contextId != null) {wac.setId(this.contextId);} else {// 否则自动生成一个 id:一般是 application:/contextPath/servletNamewac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());}}// 设置容器的 ServletContext,后续会用于获取资源、属性等wac.setServletContext(getServletContext());// 设置 ServletConfig(包含 servlet 的 init 参数等信息)wac.setServletConfig(getServletConfig());// 设置命名空间,一般用于区分不同的 DispatcherServlet 容器wac.setNamespace(getNamespace());// 添加一个监听器,用于监听容器的 refresh 事件(刷新完成时触发)wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));// 获取当前环境对象,用于管理属性配置(如 application.properties)ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {// 初始化 servletContext 和 servletConfig 中的属性源(property sources)// 这样可以在后续的 Bean 初始化过程中使用 ${...} 占位符引用这些属性((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());}// 钩子方法:可以在子类中覆写,对容器进一步自定义处理(例如注册额外 Bean 定义)postProcessWebApplicationContext(wac);// 应用 SpringApplicationContextInitializer(初始化器扩展点)applyInitializers(wac);// 最终刷新容器,触发 Bean 的加载、依赖注入、事件发布等流程wac.refresh();
}
最终调用的是AbstractApplicationContext
的refresh
方法,在refresh
完成后,XmlWebApplicationContext
的属性才有值:
1.2、onRefresh
在configureAndRefreshWebApplicationContext
方法中,还有一行关键的代码:
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
这行代码是添加了一个监听器,触发时机是Spring容器refresh完成后。
最终会跳转到
DispatcherServlet
的initStrategies
方法。
在该方法中,会进行一些初始化操作,其中最重要的是
initHandlerMappings
和initHandlerAdapters
。在说明这两个方法的逻辑之前,有必要先说明下什么是Handler。
二、请求处理器
Handler是Spring MVC中的请求处理器,有四种实现:
2.1、@RequestMapping
RequestMapping
是最常见的请求处理器实现,可以加在方法也可以加在类上,如果加在类上,需要配合@Component
注解:
@Controller
public class TestController {@Autowiredprivate TestService testService;@RequestMapping(method = RequestMethod.GET, path = "/test")public void test(){System.out.println("test");}
}
2.2、Controller接口
后三种方式在项目开发中较为少见,第一种是自定义类,实现Controller接口,同时需要将自定义的类标记成bean,然后加上请求的路径(需要加/)
@Component("/test1")
public class MyHandler implements Controller {/*** @param request current HTTP request * @param response current HTTP response* @return* @throws Exception*/@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {System.out.println("MyHandler implements Controller");return null;}
}
2.3、HttpRequestHandler接口
自定义一个类,实现HttpRequestHandler接口,和实现Controller接口的区别在于返回值为空。
@Component("/test2")
public class MyHandler1 implements HttpRequestHandler {/*** @param request current HTTP request * @param response current HTTP response* @throws ServletException* @throws IOException*/@Overridepublic void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("MyHandler1 implements HttpRequestHandler");}
}
2.4、HandlerFunction
也可以在配置类中注册一个自定义的bean,返回值类型为RouterFunction<ServerResponse>
@ComponentScan("com.zhouyu")
@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(); }}
上述这四种Handler,分别对应Spring MVC源码中DispatcherServlet.properties
:
- @RequestMapping对应
BeanNameUrlHandlerMapping
。 - Controller接口和HttpRequestHandler接口对应
RequestMappingHandlerMapping
。 - HandlerFunction对应
RouterFunctionMapping
。
三、initHandlerMappings
HandlerMapping
的作用,就是去记录请求路径
和上述Handler
的对应关系。可以理解底层有一个map,key是请求的路径,value是与之匹配的Handler对象,在容器启动时就将其维护好,避免在请求到达时再去寻找对应的Handler。
在initHandlerMappings
中,首先会去寻找有没有用户自定义的HandlerMapping
类型的bean,如果有,就会直接给DispatcherServlet
的handlerMappings
属性赋值为自定义的那个处理器,不会再去用DispatcherServlet.properties
中默认的那三个了。
如果没有自定义的,才会去调用
getDefaultStrategies
找DispatcherServlet.properties
中的:
3.1、getDefaultStrategies
在getDefaultStrategies
中,首先会拿到DispatcherServlet.properties
默认的三个处理器:
然后会按照顺序循环处理:
这里要区分不同的情况。
3.1.1、RequestMappingHandlerMapping
RequestMappingHandlerMapping
是针对@RequestMapping
注解的情况,处理逻辑的代码在afterPropertiesSet
中:
因为它间接实现了
InitializingBean
接口,所以处理时机是在bean生命周期中的初始化阶段
。我们来看一下RequestMappingHandlerMapping
重写的afterPropertiesSet
的逻辑,最终调用的是父类AbstractHandlerMethodMapping
的afterPropertiesSet
方法:
首先会拿到Spring容器中所有bean的名称,然后过滤掉"scopedTarget." 开头的 Bean,最后用当前的候选bean调用
processCandidateBean
方法:
在
processCandidateBean
方法中,会在isHandler
中进行判断,当前的bean是否有@Controller注解
或@RequestMapping注解
如果仅仅在类上加@RequestMapping注解也是不可以的,因为没有@Component,该类就不会被扫描并且注册成bean,也不会进入这一步的逻辑
对于符合要求的bean,则会进入detectHandlerMethods
方法:
其中的核心方法是
getMappingForMethod
:
/*** 为指定的方法构建对应的 RequestMappingInfo(映射信息)。* 该方法会综合考虑方法级注解、类级注解,以及类路径前缀等信息。** @param method 要处理的方法(如某个 @RequestMapping 方法)* @param handlerType 方法所属的处理器类* @return 方法对应的 RequestMappingInfo(包含路径、请求方式、参数条件等),如果该方法无映射,则返回 null*/
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {// 第一步:从方法上提取 @RequestMapping 注解信息(方法级映射)RequestMappingInfo info = createRequestMappingInfo(method);if (info != null) {// 第二步:从类上提取 @RequestMapping 注解信息(类级映射)RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);// 如果类级映射不为空,则与方法级映射合并,形成完整映射if (typeInfo != null) {info = typeInfo.combine(info);}// 第三步:获取路径前缀(比如配置了统一前缀 "/api"),追加到映射路径中String prefix = getPathPrefix(handlerType);if (prefix != null) {// 构造前缀映射信息,再与原映射 info 合并info = RequestMappingInfo.paths(prefix).options(this.config) // 使用当前的 config(包含匹配规则等).build().combine(info);}}// 返回最终合并后的 RequestMappingInforeturn info;
}
在该方法中会解析@RequestMapping
注解信息:
最终返回到
detectHandlerMethods
方法的是一个Map<Method, T> methods
,key是当前的方法对象,value是注解的信息
然后会遍历这个map:
从上图中的
registerHandlerMethod
进入MappingRegistry
的register
方法,在该方法中有两个重要的map:
- pathLookup:key存放了请求路径,value存放了注解的信息。
- registry:key存放了注解的对象,value存放了方法的信息。
到这一步为止就完成了路径与方法的映射关系
3.1.2、BeanNameUrlHandlerMapping
BeanNameUrlHandlerMapping
处理的逻辑在setApplicationContext
方法中。
因为BeanNameUrlHandlerMapping
间接实现了ApplicationContextAware
接口,所以处理时机是在bean生命周期中的初始化
前。
会调用initApplicationContext
方法:
其核心方法是
AbstractDetectingUrlHandlerMapping
的detectHandlers
,同样会先获取容器中所有的bean:
然后遍历这些bean的名称,找到以"/“开头的(之前说明过,自定义bean实现HttpRequestHandler接口或Controller接口,需要指定bean的名称,并且在最前面加上”/"代表路径)
在这一步注册映射关系
调用到registerHandler
方法,首先根据bean的名称获取到bean:
- handlerMap:key存放访问路径,value存放类对象。
最终handlerMap存放了映射关系:
四、initHandlerAdapters
initHandlerAdapters
是用于初始化DispatcherServlet
的handlerAdapters
属性的方法。initHandlerAdapters() 的作用就是准备好一组 能够适配各种Controller类型的执行器,让DispatcherServlet能够在处理请求时找到并调用合适的控制器逻辑。
和initHandlerMappings一样,首先会去找是否有自定义的HandlerAdapter,如果有,就用自定义的,并且不会用默认的了
getDefaultStrategies 同样是去DispatcherServlet.properties中找默认的策略
四种策略,分别对应<二>中的四种方式
和initHandlerMappings
一样,最终同样会去遍历DispatcherServlet.properties
中的策略,然后走创建各自bean的流程。
最终得到四个实例,因为在案例中四种请求方式都有。
这里也是适配器模式的体现,四种
HandlerAdapter
都实现了HandlerAdapter
适配器接口,真正如何适配的,会在doDispatch
(处理请求)的源码中有所体现。
总结
Spring MVC在解析web.xml,调用DispatcherServlet
的init
方法时,完成的主要工作可以分为两部分:
- 添加一个监听器,用于监听容器的 refresh 事件(刷新完成时触发)。
- 创建Spring容器,调用refresh方法。
其中Spring容器refresh 完成后,最终会被监听到,并且调用DispatcherServlet
的initStrategies
方法,在该方法中会完成HandlerMappings和HandlerAdapters的初始化。HandlerMappings用于保存路径和方法的映射关系,而HandlerAdapters让DispatcherServlet能够在处理请求时找到并调用合适的控制器逻辑。
HandlerMappings和HandlerAdapters的初始化,本质都是实例化DispatcherServlet.properties
配置文件中定义的bean名称。在各自的生命周期中进行方法回调。BeanNameUrlHandlerMapping
处理的逻辑在setApplicationContext
方法中,RequestMappingHandlerMapping
处理逻辑的代码在afterPropertiesSet
中。
相关文章:

Spring MVC源码分析のinit流程
文章目录 前言一、 init1.1、createWebApplicationContext1.2、onRefresh 二、请求处理器2.1、RequestMapping2.2、Controller接口2.3、HttpRequestHandler接口2.4、HandlerFunction 三、initHandlerMappings3.1、getDefaultStrategies3.1.1、RequestMappingHandlerMapping3.1.…...

【后端开发】go-zero微服务框架实践(goland框架对比,go-zero开发实践,文件上传问题优化等等)
【后端开发】go-zero微服务框架实践(goland框架对比,go-zero开发实践,文件上传问题优化等) 文章目录 1、go框架对比介绍2、go-zero 微服务开发实践3、go-zero 文件上传问题优化 1、go框架对比介绍 国内开源goland框架对比 1 go-…...

C#程序加密与解密Demo程序示例
目录 一、加密程序功能介绍 1、加密用途 2、功能 3、程序说明 4、加密过程 5、授权的注册文件保存方式 二、加密程序使用步骤 1、步骤一 编辑2、步骤二 3、步骤三 4、步骤四 三、核心代码说明 1、获取电脑CPU 信息 2、获取硬盘卷标号 3、机器码生成 3、 生成…...

小程序事件系统 —— 33 事件传参 - data-*自定义数据
事件传参:在触发事件时,将一些数据作为参数传递给事件处理函数的过程,就是事件传参; 在微信小程序中,我们经常会在组件上添加一些自定义数据,然后在事件处理函数中获取这些自定义数据,从而完成…...

深入解析 JavaScript 原型与原型链:从原理到应用
原型和原型链是 JavaScript 中实现对象继承和属性查找的核心机制。为了更深入地理解它们,我们需要从底层原理、实现机制以及实际应用等多个角度进行分析。 1. 原型(Prototype) 1.1 什么是原型? 每个 JavaScript 对象(…...

关于AI数据分析可行性的初步评估
一、结论:可在部分环节嵌入,无法直接处理大量数据 1.非本地部署的AI应用处理非机密文件没问题,内部文件要注意数据安全风险。 2.AI(指高规格大模型)十分适合探索性研究分析,对复杂报告无法全流程执行&…...

回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测
回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测 目录 回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测回归效果基本介绍GWO-BP-Adaboost:基于灰狼算法优化BP神经网络结合Adaboost思想…...

ARM Cortex-M 内存映射详解:如何基于寄存器直接读写 寄存器映射方式编码程序 直接操作硬件寄存器来控制 MCU
ARM Cortex-M 的系统映射空间 在 STM32 等 ARM Cortex-M 系列 MCU 中,内存地址空间按照 存储功能 进行了严格划分,包括 Flash(程序存储)、RAM(数据存储)、外设寄存器(GPIO、UART、SPI 等&am…...

深度学习实战车辆目标跟踪与计数
本文采用YOLOv8作为核心算法框架,结合PyQt5构建用户界面,使用Python3进行开发。YOLOv8以其高效的实时检测能力,在多个目标检测任务中展现出卓越性能。本研究针对车辆目标数据集进行训练和优化,该数据集包含丰富的车辆目标图像样本…...

django中视图作用和视图功能 以及用法
在 Django REST Framework(DRF)中,视图(View)是处理 HTTP 请求并返回响应的核心组件。DRF 提供了多种视图类,适用于不同的场景和需求。以下是 DRF 中常见的视图类及其作用、使用方法的详细说明: 一、DRF 视图的分类 DRF 的视图可以分为以下几类: 基于函数的视图(Func…...

【每日学点HarmonyOS Next知识】输入框自动获取焦点、JS桥实现方式、Popup设置全屏蒙版、鼠标事件适配、Web跨域
1、HarmonyOS TextInput或TextArea如何自动获取焦点? 可以使用 focusControl.requestFocus 对需要获取焦点的组件设置焦点,具体可以参考文档: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-universal-attribut…...

【学习思维模型】
学习思维模型 一、理解类模型二、记忆类模型三、解决问题类模型四、结构化学习模型五、效率与习惯类模型六、高阶思维模型七、实践建议八、新增学习思维模型**1. 波利亚问题解决四步法****2. 主动回忆(Active Recall)****3. 鱼骨图(因果图/Ishikawa Diagram)****4. MECE原则…...

MyBatis-Plus分页控件使用及使用过程发现的一个坑
最近维护一个旧项目的时候,出现了一个BUG,经排查后发现是Mybatis-plus分页控件使用的时候需要注意的一个问题,故在本地使用MybatisPlus模拟出现了一下这个问题。 首先,先说一下MyBatis-Plus的使用: 1)引入…...

STM32的APB1和APB2的区别
STM32微控制器中的APB1和APB2的区别 STM32微控制器中的APB1和APB2是两种不同的外设总线,主要区别在于时钟速度、连接的外设以及用途。以下是它们的详细对比: 1. 时钟速度 APB1 (Advanced Peripheral Bus 1): 低速总线,时钟频率通常为系统时钟…...

JS一些小知识点
一、|| 运算符 plain this.ctx.body { type: type || 0, // ||在此处用法用于默认值填充,判断是否传参或该值是否存在,如果不存在就使用||后买你的值作为默认值 code: code || 0, msg: msg || SUCCESS, data: data || {}, ...others }; 二、trim() 方…...

手写Tomcat:实现基本功能
首先,Tomcat是一个软件,所有的项目都能在Tomcat上加载运行,Tomcat最核心的就是Servlet集合,本身就是HashMap。Tomcat需要支持Servlet,所以有servlet底层的资源:HttpServlet抽象类、HttpRequest和HttpRespon…...

C#变量与变量作用域详解
一、变量基础 1. 声明与初始化 声明语法:<数据类型> <变量名>(如 int age; string name)初始化要求: 1、 类或结构体中的字段变量(全局变量)无需显式初始化,默认值…...

SV学习笔记——数组、队列
一、定宽数组 定宽数组是静态变量,编译时便已经确定其大小,其可以分为压缩定宽数组和非压缩定宽数组:压缩数组是定义在类型后面,名字前面;非压缩数组定义在名字后面。Bit [7:0][3:0] name; bit[7:0] name [3:0]; 1.1定宽数组声明 数组的声…...

API调试工具的无解困境:白名单、动态IP与平台设计问题
引言 你是否曾经在开发中遇到过这样的尴尬情形:你打开了平台的API调试工具,准备一番操作,结果却发现根本无法连接到平台?别急,问题出在调试工具本身。今天我们要吐槽的就是那些神奇的开放平台API调试工具,…...

Git清理本地残留的、但已经在服务器上被删除的分支
要筛选出已经被服务器删除的本地分支,并在本地删除这些分支,可以按照以下步骤进行操作: 步骤 1: 获取远程分支信息,确保本地的远程分支信息是最新的: git fetch -p步骤 2: 列出本地分支和远程分支: git …...

HTTPS实现内容加密的逻辑
加密过程 使用非对称加密,网站生成公钥和私钥浏览器获取到网站公钥(通过验证和解析CA证书),随即生成一串字符串,然后使用公钥加密,发送给网站。网站用私钥将加密内容解析,然后使用这串字符串对…...

使用vue3.0+electron搭建桌面应用并打包exe
使用vue3.0electron搭建桌面应用并打包exe_如何使用electron将vue3vite开发完的项目打包成exe应用程序-CSDN博客...

JSAR 基础 1.2.1 基础概念_空间小程序
JSAR 基础 1.2.1 基础概念_空间小程序 空间空间自由度可嵌入空间空间小程序 最新的技术进展表明,官网之前的文档准备废除了,基于xsml的开发将退出历史舞台,three.js和普通web结合的技术将成为主导。所以后续学习请移步three.js学习路径&#…...

mysql练习
创建数据库db_ck,再创建表t_hero,将四大名著中的主要人物都插入这个表中,将实现过程中sql提交上上来 1、创建数据库db_ck mysql> create database db_ck; 2、创建表t_hero mysql> use db_ck Database changed mysql> create table …...

2025年2月平价旗舰手机性能对比
1、荣耀Magic7 点评:缺席潜望式长焦,3X直立长焦体验还行。兼顾性能、游戏、屏幕、影像、续航、快充等诸多方面,且外围配置比较齐全。 2、vivo x200 点评:潜望式长焦相机,拍照效果好,30W无线充电着实鸡肋&a…...

基于Spring Boot的扶贫助农系统的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...

物联网中如何增加其可扩展性 协议 网络 设备 还包括软件层面上的
物联网(IoT)系统的可扩展性是指系统能够随着设备数量、数据流量和业务需求的增长而灵活扩展的能力。为了增加物联网的可扩展性,需要从协议、网络、设备和软件等多个层面进行优化和设计。以下是一些具体的策略和方法: 1. 协议层面的可扩展性 1.1 采用轻量级协议 轻量级协议…...

基于DeepSeek与搜索引擎构建智能搜索摘要工具
基于DeepSeek与搜索引擎构建智能搜索摘要工具 1. 项目概述 本项目通过整合DuckDuckGo搜索引擎与DeepSeek大语言模型,实现了一个智能搜索摘要生成工具。系统可自动执行以下流程: 输入查询语句进行全网搜索获取并解析搜索结果调用AI模型生成结构化摘要输出带来源标注的专业级…...

SQL 简介
SQL 简介 引言 结构化查询语言(Structured Query Language,简称 SQL)是一种用于数据库管理和操作的标准查询语言。它广泛应用于各个领域的数据库管理系统(DBMS)中,用于存储、检索和管理数据。SQL 不仅是数…...

BLUEM2引擎源码2025最新版
BLUE 引擎解析:传奇私服圈中的热门引擎 一、BLUE 引擎简介 BLUE 引擎是传奇私服圈子中较为知名的一款游戏引擎,它在传统的传奇引擎基础上进行了优化和扩展,使得私服开发者可以更加方便地搭建和管理服务器。相比于早期的 GEE、LEG、Hero 等引…...