揭秘SpringMVC-DispatcherServlet之九大组件(二)

news2024/12/25 9:00:12

前言

上回聊到了HandlerAdapter,今天继续聊后面的组件。今天的主角是HandlerMapping,这篇文章全为他服务了。

HandlerMapping

上回说的Handler,我们说是处理特定请求的。也就是说,不是所有的请求都能处理。那么问题来了,我们怎知道哪个请求是由哪个Handler处理的呢?
噔噔当,有请HandlerMapping闪亮登场。HandlerMapping就是处理uri到handler的映射的。从这个角度看他与Map有几分相似,都是key-value。然鹅,并不一样!Spring的命名,你不得不服,就这点也给你整的明明白白。他是Handler+Mapping。是映射Handler没错,但不是Map。我们的Handler可以处理特定的请求没错,但没说只能处理一个特定请求啊。也可能是一个类似的patern的一系列路径。先看看定义:

public interface HandlerMapping {
	/**
	 * 获取Handler
	 */
	@Nullable
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}


public interface MatchableHandlerMapping extends HandlerMapping {
	/**
	 * 匹配请求路径
	 */
	@Nullable
	RequestMatchResult match(HttpServletRequest request, String pattern);
}

我们可以发现,他有两个接口,其中MatchableHandlerMapping是HandlerMapping的派生接口。多了请求匹配接口方法。
这里介绍以下他的三个重要实现:

实现介绍
SimpleUrlHandlerMapping用于简单映射URL到Handler。主要提供给客户定制使用的,因此不会自动配置
BeanNameUrlHandlerMapping如果Handler的beanName带“/”的,就是由他来进行映射的。他会自动扫描容器中的beanName来识别handler,自动建立映射关系。在没有配置HandlerMapping的情况下,会自动配置。
RequestMappingHandlerMapping看名字就知道,他是为了@RequestMapping进行映射的。在没有配置HandlerMapping的情况下,会自动配置

SimpleUrlHandlerMapping

在这里插入图片描述
PS:社区版只能这样看class diagrams了,勉强看吧。旗舰版的氪金大佬可以右键看氪金版的。
言归正传,SimpleUrlHandlerMapping特别简单,只要告诉他哪个请求由哪个handler处理就行。可以调用他的setMapping(Properties mapping)方法,或者是setUrlMap。前者会被转成Map给属性urlMap赋值,而后者则是直接对urlMap赋值。
可参考大佬的文章:SimpleUrlHandlerMapping用法
从继承关系看,他继承于AbstractUrlHandlerMapping。
AbstractUrlHandlerMapping实现了一系列基于url实现的HandlerMapping通用功能。主要体现在,他通过ApplicationContextAware接口获取到ApplicationContext,并且在该方法中获取handler的beanName并转换为实际handler对象。并注册到org.springframework.web.reactive.handler.AbstractUrlHandlerMapping#handlerMap。得,到这里,应该知道,我们是在什么时候建立起URL跟Handler的关系了吧。

BeanNameUrlHandlerMapping

相较于SimpleUrlHandlerMapping,BeanNameUrlHandlerMapping则完全不需要用户自己定义url到handler的关系。,而是自动检测发现。前提是,你的handler在定义的时候beanName以“/”开头。而关于他的秘密也同样在他的继承关系图里:
在这里插入图片描述

与SimpleUrlHandlerMapping类似,他也是基于ApplicationContextAware接口进行扩展而来的能力。在获得上下文后,通过上下文遍历所有的beanName,就能知道这个bean是不是一个handler了。

小结

上面讲的都是基于AbstractUrlHandlerMapping扩展出来的HandlerMapping。而AbstractUrlHandlerMapping处理的都是基于URL来处理的,意味着不会有其他的可以匹配的条件。Handler注册中心则有两种。

Handler注册方式Handler注册中心
基于URL 注册Map<String, Object> handlerMap = new LinkedHashMap<>();
基于pattern注册的Map<PathPattern, Object> pathPatternHandlerMap = new LinkedHashMap<>();

但都是简单Map就能搞定。

RequestMappingHandlerMapping

终于是到我们心心念念的@RequestMappingHandlerMapping了。为什么不叫MethodHandlerMapping呢?主要还是Handler是一个逻辑概念,MethodHandler了只是对目标方法进行了封装,并不是真正处理请求的。真正处理请求的是我们@RequestMapping的方法。还是那句话,取个名字都给你讲道理。

PS: 接下来会花很大篇幅来介绍他,因为他很重要绕不过去,他是我们常用的@RequestMapping的映射器。又因为功能强大而无法像上面的映射器那样三言两语讲清楚。想要让大家了解请求他的设计,只能花大功夫了。

前面我们知道HandlerMapping是用来寻找Handler的,并不与Handler的类型或者实现绑定,而是根据需要定义的。那么为什么要单独给@RequestMapping实现一个HandlerMapping?答案就在@RequestMapping本身。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
	/**
	 * 处理器的名字,支持类级别和方法级别,多个路径用#分割
	 */
	String name() default "";
	/**
	 * 匹配请求路径
	 */
	@AliasFor("path")
	String[] value() default {};
	/**
	 * 匹配请求路径
	 */
	@AliasFor("value")
	String[] path() default {};
	/**
	 * Http请求方法:可选GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE
	 */
	RequestMethod[] method() default {};
	/**
	 * 匹配指定的地址栏参数
	 */
	String[] params() default {};

	/**
	 * 匹配特定的header
	 */
	String[] headers() default {};
	/**
	 * 匹配特定的Content-Type
	 */
	String[] consumes() default {};
	/**
	 * 匹配特定的Accept
	 */
	String[] produces() default {};
}

发现了吗,各位?他除了能根据URI匹配,还能根据请求头、请求方法、甚至是请求参数来匹配!前面说的两种HandlerMapping都不能满足他,因此必须推出一个更加强大、可扩展性更强的HandlerMapping——RequestMappingHandlerMapping
在这里插入图片描述
那么问题来了:

  • @RequestMapping是在什么如何被解析的呢?
    我们很容易想到的就是,遍历容器中所有的对象,检查是否存在@Controller注解。存在,那就是控制器。然后接着遍历所有声明的public方法,检查是否存在@RequestMapping方法。这样,我们就找到了处理器方法。
  • @RequestMapping是在什么时候被解析的呢?
    本着“谁使用,谁解析”的原则,他自然是被RequestMappingHandlerMapping解析的。而又因为@RequestMapping的寻找可太费功夫,不可能在提供映射服务时再来解析,只能是初始化时进行解析。因此实现InitializingBean进行初始化,是个选择。

没错,实际上,SpringMVC跟你想的一样。在InitializingBean的afterPropertiesSet方法中,完成了以3下件事:

  1. 寻找@Controller的bean,并找到所有的@RequestMapping方法
  2. 解析@RequestMapping封装成RequestMappingInfo
  3. 将以上解析到的信息进行注册。信息包括:
    信息描述
    @Controller/@RequestMapping对象反射调用目标方法时,需要的target对象
    RequestMappingInfo由@RequestMapping解析而来
    @RequestMapping的方法注册时,注册器会将Method与handler对象一起封装成HandlerMethod进行注册。便于后面适配器调用。

@RequestMapping的注册

前面的解析@RequestMapping到RequestMappingInfo,可以省略,比较简单。但是@RequestMapping的注册没办法省略。因为如果搞不清楚他是怎么注册的,也就没办法理解他是怎么寻找目标处理器的。

为了支撑@RequestMapping多样化的匹配条件,不能再像前面两款HanderMapping一样,简单粗暴的使用Map了。在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping的内部定义了内部类,专门用来做注册中心,管理映射关系。

	/**
	 * Mapping注册中心,可以理解为办事处
	 */
	class MappingRegistry {
		/**
		 * T是匹配条件的对象。MappingRegistration是注册的信息,可以理解为你要做事情。
		 * 对于RequestMappingHandlerMapping,T就是RequestMappingInfo
		 * MappingRegistration包括信息:RequestMappingInfo、HandlerMethod、directPaths、mappingName、corsConfig
		 */
		private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
		/**
		 * Map<path, RequestMappingInfo>
		 */
		private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();

		/**
		 * Map<mappingName, List<HandlerMethod>>
		 */
		private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
	}

从他的属性,我们可以分析得到如下信息:

  1. 直接通过请求路径来查找处理器时,需要经过pathLookup中转registry,最后拿到MappingRegistration才到达HandlerMethod.
  2. 直接通过mappingName则可以直接找到HandlerMethod. 不过这个是Spring为了支持<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>而提供的。跟我们平时使用没多大关系。

但是各位观众老爷,pathLookup找到的是一个,而mappingName能找到多个,这是咋回事?误会啊,pathLookup的value可不是简单的一个元素,而是多个!他是MultiValueMap,不是我们经常看到的地摊货HashMap。他可以一个key对应多个value。

但是为什么会有多个呢?或者说为什么需要保存多个呢?
因为一个Handler可以处理多个请求,如果由多个Handler都能处理某一个请求的时候怎么办呢?况且SpringMVC还支持通配符匹配。umm…这一幕有点似曾相识,我们的nginx做路由转发的时候,不是也有类似的问题吗?这意味着在查找Handler的时候,我们还需要找到最佳的匹配。例如,/*相较于/hello,那肯定是/hello更精确,更合适啦。你看,多严谨!

得嘞,到这里我们就基本搞清楚RequestMappingHandlerMapping的初始化,以及他是怎么找到目标Handler了。

封装HandlerExecutionChain

此时,必须提醒一下大家,HandlerMapping接口的返回值是HandlerExecutionChain,并不是Handler。前面不管是说哪个HandlerMapping,都只是在说怎么寻找Handler、以及怎么注册。可没有说HandlerExecutionChain。其实,这得益于他们公共的父类方法org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler。这也意味着,封装HandlerExecutionChain是在每个请求进来的时候才封装的。具体代码:

	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(request)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}

代码也比较简单。这里面还让我们多认识了MappedInterceptor,他可以针对特定的请求进行拦截。

好了,终于是把HandlerMapping讲完了。

后记

RequestMappingHandlerMapping使用了InitializingBean做初始化,但是当我们自己在做初始化的时候,尤其是使用多种初始化方式的时候,应当要注意Spring的调用顺序,否则有可能发生NPE,或者获取不到目标属性的情况。例如:同时在ApplicationContextAware、InitializingBean、@PostConstruct进行初始化。
为此,给大家找了官方的bean的生命周期
在这里插入图片描述

上一篇:
揭秘SpringMVC-DispatcherServlet之九大组件(一)
第一篇:
揭秘SpringMVC-DispatcherServlet启动-web上下文

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

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

相关文章

gateway初始化与配置

1、排除依赖 spring-boot-starter-webflux 2、添加依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency><groupId>org.springf…

基于GDAL的JAVA生成GDB文件实战

前言 在之前博客中&#xff0c;陆续的介绍了关于gdb文件的读取&#xff0c;gis利器之Gdal&#xff08;三&#xff09;gdb数据读取&#xff0c;玩转GDAL一文带你深入Windows下FileGDB驱动支持&#xff0c;这些文章主要都是介绍gdal的读取gdb以及简单的gdb文件读写。在实际工作中…

VJ个人周赛

A:模拟 题意&#xff1a;给定了N个任务&#xff0c;每个任务都有一个优先级&#xff08;1~9&#xff09;&#xff0c;数字越大&#xff0c;优先级越高。将这些任务放入队列中&#xff0c;如果出队的元素&#xff08;x&#xff09;&#xff0c;x的优先级不是最高的&#xff0c;那…

从高级测试到测试开发有什么感悟

最近加入了新的团队&#xff0c;角色发生较大的转变&#xff0c;在这里分享一下自己的感受。 测试的划分 如果我们把产品的生产看成一个流水线的话&#xff0c;那么测试就是流水线上的一个重要岗位&#xff0c;把控着产品的质量。 当然&#xff0c;产品类型的不同&#xff0…

信息系统安全管理

信息系统安全是一个绕不开的话题。从事IT行业&#xff0c;不论何种角色&#xff0c;哪个工种&#xff0c;都需要有所了解。 一、信息系统安全策略 1、概述 信息系统安全策略是指对&#xff08;本单位&#xff09;信息系统的安全风险&#xff08;安全威胁&#xff09;进行有效…

小白学编程(js):通过按钮变换背景颜色

《JavaScript从入门到精通》【例9.1】 代码演示&#xff1a; <body><form class"form1" action"" name"form1" method"psot"><p><input type"button" name"Submit" value"变换背景&qu…

[附源码]计算机毕业设计基于Java的图书购物商城Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Linux常用环境配置及软件安装(持续更新)

1、jdk 1、下载jdk Linux安装包 把安装包放到自己定义的目录下 安装包网盘 提取码&#xff1a;n5hj 2、解压 解压安装包&#xff0c;输入命令&#xff1a; tar -xvf jdk-8u221-linux-x64.tar.gz 解压完成后会生成一个新文件 3、配置环境变量 编辑profile文件 vim /etc/p…

基于java+springboot+mybatis+vue+mysql的高校党务系统

项目介绍 本党务管理系统主要包括五大功能模块&#xff0c;即管理员模块、学生模块、积极分子模块、党员、党建组织。 &#xff08;1&#xff09;管理员模块&#xff1a;主要功能有&#xff1a;首页、个人中心、学生管理、学院管理、专业管理、班级管理、积极分子管理、党员管…

LeetCode HOT 100 —— 208. 实现 Trie (前缀树)

题目 Trie&#xff08;发音类似 “try”&#xff09;或者说 前缀树 是一种树形数据结构&#xff0c;用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景&#xff0c;例如自动补完和拼写检查。 请你实现 Trie类&#xff1a; Trie() 初始化前缀树对象。 vo…

postgresql_internals学习笔记(二)常规vacuum

一、 作用与原理 page pruning执行速度很快&#xff0c;但它们的作用范围毕竟只有单页、且不包含索引&#xff0c;因此&#xff0c;我们还需要更有效的清理机制。 常规vacuum是最常用的一种&#xff0c;作用范围可以是整张表&#xff0c;清理过期元组及索引项&#xff0c;并且不…

PS图层+移动工具(1)图层概念-拖动操作-移动工具基础

先打开ps软件 然后点击进入工作区 选择右上角文件 点击打开 随便选一个要操作的图片 然后看一下自己工作区右侧的 这个图层工具开了没有 如果没开 点击上方 窗口 将图层选项勾选上 这里可以看到 我们打开一个完整图片 他就只有一个图层 触发你打开的是PSD格式的图片 psd是ps…

【云计算与大数据技术】云交付模型、云部署模型、云计算优势与挑战、应用的讲解(超详细必看)

一、云交付模型 云计算主要分为三种交付模型&#xff0c;而且这三种交付模型主要是从用户体验的角度出发的&#xff0c;分别是软件即服务&#xff08;SaaS&#xff09;&#xff0c;平台即服务&#xff08;PaaS&#xff09;&#xff0c;基础设施即服务&#xff08;IaaS&#xf…

数据库建表的 15 个最佳实践方式

前言 对于后端开发同学来说&#xff0c;访问数据库&#xff0c;是代码中必不可少的一个环节。 系统中收集到用户的核心数据&#xff0c;为了安全性&#xff0c;我们一般会存储到数据库&#xff0c;比如&#xff1a;mysql&#xff0c;oracle等。 后端开发的日常工作&#xff…

string的模拟实现

目录 ​一、模拟实现中类的组织 二、默认成员函数 1.默认构造函数 2.拷贝构造函数 &#xff08;1&#xff09;传统写法——循规蹈矩 &#xff08;2&#xff09;现代写法——偷天换日 3.析构函数 4.赋值运算符重载 二、元素访问 三、容量操作 1.容量与有效数据 2.改…

SpringBootStarter技术:生产就绪与环境配置、实现自定义Starter

● Spring 官 方 Starter &#xff1a; 命 名 应 遵 循 spring-boot-starter-{name} 的 格 式 &#xff0c; 如 spring-boot-starter-web 作 为 SpringBoot Web模块的官方artifactId。 ● Spring 非 官 方 Starter &#xff1a; 命 名 应 遵 循 {name}-spring-bootstarter的格…

ModBus_RTU-上位机经RS485接口与PLC通信

目录&#xff1a; 一、预备知识 二、上位机经RS485接口与PLC通信 ---------------------------------------------------------------------------------------------------------------------- 一、预备知识 电力-ModBus_RTU通讯规约1 电力-ModBus_RTU通讯规约2 通信-R…

Java基于springboot+vue足球联赛管理系统

本足球联赛管理系统是针对目前足球联赛管理的实际需求&#xff0c;从实际工作出发&#xff0c;对过去的足球联赛管理系统存在的问题进行分析&#xff0c;完善用户的使用体会。采用计算机系统来管理信息&#xff0c;取代人工管理模式&#xff0c;查询便利&#xff0c;信息准确率…

字符串处理【后缀数组】 - 原理2 后缀数组

字符串处理【后缀数组】 - 原理2 后缀数组 在字符串处理中&#xff0c;后缀树和后缀数组&#xff08;Suffix Array&#xff09;都是非常有力的工具。 后缀数组是后缀树的一个非常精巧的替代品&#xff0c;比后缀树容易实现&#xff0c;可以实现后缀树的很多功能&#xff0c;时…

Jenkins

目录 一、什么是Jenkins 二、为什么需要使用持续集成工具 三、如何搭建jenkins服务 四、jenkins集成服务器上的JDK 五、jenkins集成git 5.1 jenkins所在的服务安装git 5.2 jenkins集成git 5.3 jenkins创建一个任务项 5.4 创建远程仓库 5.5 执行任务 六、jenkins集成maven…