《学会 SpringMVC 系列 · 剖析初始化》

news2025/1/23 4:00:26

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

CSDN.gif

写在前面的话

前几篇博文,大致了解了SpringMVC请求流程中的源码分析和扩展运用,为知识连贯性,本篇介绍一下 SpringMVC 初始化过程中的相关源码解读。
和请求流程相比,初始化流程更为简单,主要就是初始化得到一些准备数据,为后续请求过程服务。

相关博文
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《学会 SpringMVC 系列 · 剖析出参处理》
《学会 SpringMVC 系列 · 返回值处理器》
《学会 SpringMVC 系列 · 消息转换器 MessageConverters》
《学会 SpringMVC 系列 · 写入拦截器 ResponseBodyAdvice》
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》


SpringMVC 初始化

前文提要

前面请求篇介绍的时候,提到 DispatcherServlet 是 SpringMVC 的入口,不管是否为 SpringBoot 项目,都是如此。
从下面这张图很明显可以看出 DispatcherServlet 和 Servlet 的父子关系。
image.png
有过 JavaWeb 开发经验的人应该了解,Servlet 的初始化是 inti 方法。这边先不展开细节,后续再按专栏展开,总之是在 DispatcherServlet、FrameworkServlet、HttpServlet 等类之间反复横跳。
可以先将入口判定为 HttpServletBean#initServletBean。

启动流程

Tips:DispatcherServlet 本质就是一个 Servlet,遵循Servlet的初始化和请求规范。

1、启动 Tomcat
2、解析 web.xml
3、创建并初始化 DispatcherServlet
4、触发 DispatcherServlet 父类的init方法(比如 HttpServletBean#initServletBean)
5、FrameworkServlet#initWebApplicationContext(这步接下来是核心,创建Web上下文)
6、FrameworkServlet#configureAndRefreshWebApplicationContext(配置文件的解析,扫描和加载Bean)
7、DispatcherServlet#initStrategies(初始化HandleMapping等内容,代码如下)

Tips:创建容器的过程类似Spring,没什么特殊的,这边暂不展开。

@Override
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}
/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

最后一步用到了Spring的发布订阅,FrameworkServlet 内部有如下代码,最终触发到了 DispatcherServlet 的 onRefresh。

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		FrameworkServlet.this.onApplicationEvent(event);
	}
}

initHandlerMappings

Tips:nitStrategies 里面方法很多,比较重要的,或者说和后续请求流程关联较多的,这里挑选介绍。

如方法的名字,是初始化 HandlerMappings,会加载自定义的,也会加载默认的 HandlerMappings。
自定义场景较少,默认的话是读取 DispatcherServlet.properties 文件里面的,如下所示。

private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;

	if (this.detectAllHandlerMappings) {
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}

	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	if (this.handlerMappings == null) {
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}

	for (HandlerMapping mapping : this.handlerMappings) {
		if (mapping.usesPathPatterns()) {
			this.parseRequestPath = true;
			break;
		}
	}
}

private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

image.png
由于 RequestMappingHandlerMapping 的父类,实现了 InitializingBean 接口,如下所示(圈出来的几个东西后续要关注的)。
所以会被在创建 RequestMappingHandlerMapping 的 bean 对象的时候,系统会触发其 afterPropertiesSet 方法
image.png
几经辗转,走到父类AbstractHandlerMethodMapping的如下方法,这里判断isHandler方法,代表符合要求才处理。

protected void processCandidateBean(String beanName) {
	Class<?> beanType = null;
	try {
		beanType = obtainApplicationContext().getType(beanName);
	}
	catch (Throwable ex) {
		// An unresolvable bean type, probably from a lazy bean - let's ignore it.
		if (logger.isTraceEnabled()) {
			logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
		}
	}
	if (beanType != null && isHandler(beanType)) {
		detectHandlerMethods(beanName);
	}
}

可以看到RequestMappingHandlerMapping的isHandler是判断包含Controller注解才处理。

protected boolean isHandler(Class<?> beanType) {
	return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
}

如果类符合要求,进一步就是处理该类的方法,这边会继续判断方法是否包含RequestMapping注解,然后解析注解信息,参考如下代码。
不继续深入展开,有需要再深入。

protected void detectHandlerMethods(Object handler) {
	Class<?> handlerType = (handler instanceof String beanName ?
			obtainApplicationContext().getType(beanName) : handler.getClass());

	if (handlerType != null) {
		Class<?> userType = ClassUtils.getUserClass(handlerType);
        // 得到一个Map,Key是Method,Value是RequestMappingInfo(注解信息)
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
					try {
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
						throw new IllegalStateException("Invalid mapping on handler class [" +
								userType.getName() + "]: " + method, ex);
					}
				});
		if (logger.isTraceEnabled()) {
			logger.trace(formatMappings(userType, methods));
		}
		else if (mappingsLogger.isDebugEnabled()) {
			mappingsLogger.debug(formatMappings(userType, methods));
		}

        //遍历上面的Map,得到新的Map,Key是URL,Value是Method(包含很多信息,MappingRegistry)
		methods.forEach((method, mapping) -> {
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

initHandlerAdapters

前面逻辑基本和HandleMappings一致,触发RequestMappingHandlerAdapter的afterPropertiesSet方法。
这个方法就加载了很多东西,方法子方法的名字大概也可以猜到。
请求流程里面涉及的@ControllerAdvice、消息转换器、参数解析器、返回值处理器等等,都涉及到了,应有尽有。

@Override
public void afterPropertiesSet() {

    //处理加了@ControllerAdvice注解的
	initControllerAdviceCache();

    //初始化消息转换器
	initMessageConverters();

	if (this.argumentResolvers == null) {
		List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
		this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	if (this.initBinderArgumentResolvers == null) {
		List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
		this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	if (this.returnValueHandlers == null) {
		List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
		this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
	}
	if (BEAN_VALIDATION_PRESENT) {
		List<HandlerMethodArgumentResolver> resolvers = this.argumentResolvers.getResolvers();
		this.methodValidator = HandlerMethodValidator.from(
				this.webBindingInitializer, this.parameterNameDiscoverer,
				methodParamPredicate(resolvers, ModelAttributeMethodProcessor.class),
				methodParamPredicate(resolvers, RequestParamMethodArgumentResolver.class));
	}
}

回到请求流程

初始化流程没有什么复杂的,不一一展开,浪费大家时间。
经过上面两个初始化方法,大概拿到了一些准备数据,现在再回到请求流程的核心方法 DispatcherServlet#doDispatch,代码如下所示,其实就是三步骤:找HandleMapping、找HandlerAdapter、执行HandlerAdapter的handle方法,这个过程中,用到的都是各种初始化后得到的东西。

// 找出处理这个请求的执行链对象 HandlerExecutionChain,包含方法和拦截器
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
	noHandlerFound(processedRequest, response);
	return;
}

// 根据 HandlerMethod 获取处理器 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// 执行实际的处理器处理请求,这步骤是最核心的
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

总结陈词

此篇文章介绍了SpringMVC 初始化相关的分析,仅供学习参考。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

CSDN_END.gif

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

win10系统资源监视器磁盘蓝黄线、活动时间代表什么意思?

win10系统资源监视器磁盘蓝黄线、活动时间代表什么意思&#xff1f;在win10系统中磁盘在运行工程中会有不同的数据信息显示&#xff0c;如下图所示&#xff0c;10兆字节/秒、磁盘中蓝线和黄线、活动时间都达标什么意思呢&#xff1f;本文中winwin7小编给大家分享介绍下win10系统…

Python 爬虫入门(六):urllib库的使用方法

Python 爬虫入门&#xff08;六&#xff09;&#xff1a;urllib库的使用方法 前言1. urllib 概述2. urllib.request 模块2.1 发送GET请求2.2 发送POST请求2.3 添加headers2.4 处理异常 3. urllib.error 模块4. urllib.parse 模块4.1 URL解析4.2 URL编码和解码4.3 拼接URL 5. ur…

MySQL主从复制原理及实现教程

MySQL主从复制是一种数据复制技术&#xff0c;通过建立主服务器&#xff08;Master&#xff09;与从服务器&#xff08;Slave&#xff09;之间的数据同步&#xff0c;实现数据的备份、负载均衡和高可用性。 主从复制原理 MySQL binlog(binary log 即二进制日志文件) 主要记录…

Python 如何创建和操作矩阵?

在Python中&#xff0c;矩阵是二维数组的一种常见表示方式&#xff0c;特别是在数学和科学计算领域。矩阵用于表示和操作多维数据&#xff0c;包括数据分析、图像处理、机器学习、以及物理模拟等诸多领域。Python的NumPy库是处理矩阵的主要工具之一。NumPy提供了高效的多维数组…

Deformable Detr

参考&#xff1a; https://search.bilibili.com/all?vt68804228&keywordco-detr&from_sourcewebtop_search&spm_id_from333.1007&search_source5 详解可以看李宏毅的transformer视频。

SAP MM维护采购信息记录只到采购组织层级时候,税码输入报错 MESSAGE 06388

原因&#xff1a;税确认应该是在工厂层级 解决&#xff1a;对06388 消息号进行更改类型改成W

Java-文件操作和IO

文件介绍 文件本身有多重含义,狭义的文件,特指硬盘上的文件(以及保存文件的目录),广义的文件:计算机上的很多硬件设备,软件资源,在操作系统中,都会被视为是"文件" 文件除了有数据内容之外,还有一部分信息,例如文件名,文件类型,文件大小,这些信息可以称作文件的元信…

Redis-管道

面试题 如何优化频繁命令往返造成的性能瓶颈 Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。一个请求会遵循以下步骤: 1 客户端向服务端发送命令分四步(发送命令-命令排队一命令执行-返回结果)&#xff0c;并监听Socket返回&#xff0c;通常以阻塞模式等待服…

看完这八本AI产品经理书籍,offer收的手软,收藏我这一篇就够了

对于想要深入了解人工智能领域并成为一名优秀的人工智能产品经理的专业人士来说&#xff0c;选择合适的书籍至关重要。下面是我为您精心挑选的八本关于AI产品经理领域的书籍&#xff0c;它们涵盖了从基础知识到高级实践的各个方面&#xff0c;旨在帮助读者构建全面的知识体系。…

情人节变情人劫?三好夫人:为何好男人怕过节

真正的爱&#xff0c;是两颗心灵的相互靠近&#xff0c;是理解、尊重与支持的结晶。———三好夫人 七夕节来源 2024年8月10是中国传统的情人节--七夕。七夕又称七巧节、七姐节、女儿节。妇女们在这一天相约&#xff0c;穿针乞巧&#xff0c;投针验巧&#xff0c;祈求得到智慧…

三十种未授权访问漏洞复现 合集( 一 )

未授权访问漏洞介绍 未授权访问可以理解为需要安全配置或权限认证的地址、授权页面存在缺陷&#xff0c;导致其他用户可以直接访问&#xff0c;从而引发重要权限可被操作、数据库、网站目录等敏感信息泄露。---->目录遍历 目前主要存在未授权访问漏洞的有:NFS服务&a…

全网最清晰Endnote教程笔记,轻松搞定文献管理以及文献引用

提示&#xff1a;从安装到使用&#xff0c;全网最清晰EndNote教程&#xff0c;轻松搞定文献管理&#xff01;_哔哩哔哩_bilibili 目录 前言 1 导入文献 1.1 文献数据库建立 1.2 正式导入文献 1.3 Endnote导入PDF文献 2 管理文献 3. 引用文献 3.1 Endnote预览参考文献样…

搭建 Rancher 服务,配置k8s集群

1. 前提条件 前提条件&#xff1a; 安装docker&#xff0c;要求版本各节点版本一致。网上还有额外的要求&#xff1a;关闭swap、禁用selinux等等。 2. 搭建 Rancher 服务 直接通过docker命令实现即可&#xff0c;很方便。 docker run -d \--name rancher \--restart unles…

数学建模-数据预处理(数据清洗、标准化)

在数学建模的比赛中&#xff0c;数据分析类的题目中往往我们要面对大量的数据&#xff0c;在给出的数据中&#xff0c;如果数据缺失会影响到判断和后序的操作 这里我们给出一个数据预处理的办法&#xff1a;数据清洗&#xff08;Data Clearning&#xff09;&#xff1a;涉及到…

良心推荐——揭秘10款文件加密软件!(小编真心推荐!好用!)

文件加密不仅能够有效防止敏感数据被非法获取或篡改&#xff0c;还能在数据丢失或被盗时降低损失。 因此&#xff0c;选择一款高效、可靠的文件加密软件成为了企业保障信息安全不可或缺的一环。 今天&#xff0c;小编就为大家揭秘10款良心推荐的文件加密软件&#xff0c;其中首…

BurpSuite教程——渗透测试第一关:BP工具使用

想要学好网络渗透&#xff0c;了解并熟练使用渗透工具是重要的一关。 Burp Suite 是一款广泛使用的网络安全工具&#xff0c;主要用于测试Web应用程序的安全性。它提供了一系列的功能&#xff0c;帮助安全专家和渗透测试人员发现和利用Web应用程序中的安全漏洞。 目录 一、前…

JetBrains GoLand单元测试不支持单个单元测试case执行

譬如函数代码 func AddInt(a, b int32) int32 {return a b } 单元测试代码&#xff1a; func TestAddInt(t *testing.T) {type args struct {a int32b int32}tests : []struct {name stringargs argswant int32}{{name: "add",args: args{a: 1, b: 2},want: 3},{n…

安卓自定义控件

文章目录 引入布局创建自定义控件 引入布局 首先创建一个项目&#xff0c;创建一个空的活动。然后右键单击res/layout创建一个Layout Resource File文件&#xff0c;取名title.xml。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmln…

【Android Studio】彻底卸载

文章目录 卸载程序控制面板卸载安全软件卸载 删除文件重启计算机 我们在Android开发时涉及重装时&#xff0c;如果卸载不干净&#xff0c;再次安装是不会正常运行项目的&#xff0c;接下来就让我教你如何删除干净吧。 卸载程序 控制面板卸载 control控制面板一>程序一>…

算法通关:016:设计循环双端队列

文章目录 题目思路代码运行结果问题为什么能直接调用方法名 题目 leetcode641 设计循环双端队列 思路 代码 import java.util.Deque; import java.util.LinkedList;/*** Author: ggdpzhk* CreateTime: 2024-08-03* 641 双端队列&#xff1a;利用双向链表和动态数组实现*/ pu…