源码解析SpringMVC之RequestMapping注解原理

news2024/11/18 10:49:04

1、启动初始化

核心:得到应用上下文中存在的全部bean后依次遍历,分析每一个目标handler & 目标方法存在的注解@RequestMapping,将其相关属性封装为实例RequestMappingInfo。最终将 uri & handler 之间的映射关系维护在类AbstractHandlerMethodMapping中的内部类RequestMappingInfo中。

利用RequestMappingHandlerMappingInitializingBean接口特性来完成请求 uri & handler 之间的映射关系。具体详情参考其父类AbstractHandlerMethodMapping实现其功能,如下:

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
	//内部类
	private final MappingRegistry mappingRegistry = new MappingRegistry();
	
	protected void initHandlerMethods() {
		for (String beanName : getCandidateBeanNames()) {//从应用上下文中获取全部的bean
			processCandidateBean(beanName);
		}
	}
	
	protected void processCandidateBean(String beanName) {
		Class<?> beanType = obtainApplicationContext().getType(beanName);
		// 判断是否为Handler的条件为:是否存在注解Controller 或者 RequestMapping
		if (beanType != null && isHandler(beanType)) {
			detectHandlerMethods(beanName);
		}
	}
		
	protected void detectHandlerMethods(Object handler) {//handler为String类型的beanName
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());
		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
			// 集合methods其key:反射中Method类。value:RequestMappingInfo
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						// 调用具体的实现类,例如RequestMappingHandlerMapping
						return getMappingForMethod(method, userType);
					});
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}
	
	protected void registerHandlerMethod(Object handler, Method method, T mapping) {
		this.mappingRegistry.register(mapping, handler, method);
	}
}

1.1、RequestMappingHandlerMapping

解析目标类 & 目标方法之@RequestMapping注解相关属性。

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping{
	@Override
	@Nullable
	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
		//将目标方法存在的@RequestMapping注解相关属性封装为RequestMappingInfo
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
			//如果目标handler也存在@RequestMapping注解,则也封装为RequestMappingInfo
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) {
				// 将目标方法@RequestMapping相关属性与目标handler之@RequestMapping相关属性合并起来
				info = typeInfo.combine(info);
			}
			String prefix = getPathPrefix(handlerType);//获取目标handler之url前缀
			if (prefix != null) {
				info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
			}
		}
		return info;
	}
	
	private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
		RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
		return (requestMapping != null ? createRequestMappingInfo(requestMapping, null) : null);
	}
	
	protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping,RequestCondition condition) {
		// 获取某个具体目标方法其@RequestMapping注解相关属性,最终封装为RequestMappingInfo
		RequestMappingInfo.Builder builder = RequestMappingInfo
				.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
				.methods(requestMapping.method())
				.params(requestMapping.params())
				.headers(requestMapping.headers())
				.consumes(requestMapping.consumes())
				.produces(requestMapping.produces())
				.mappingName(requestMapping.name());
		return builder.options(this.config).build();
	}
}

1.2.MappingRegistry

public abstract class AbstractHandlerMethodMapping<T> {

	class MappingRegistry {
		private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
		//集合mappingLookup之key:RequestMappingInfo。value:HandlerMethod
		private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
		private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
		
		public void register(T mapping, Object handler, Method method) {
			...
			// 目标类的目标方法最终封装为HandlerMethod  handler实为目标bean的beanName
			HandlerMethod handlerMethod = createHandlerMethod(handler, method);
			this.mappingLookup.put(mapping, handlerMethod);
			List<String> directUrls = getDirectUrls(mapping);
			for (String url : directUrls) {
				this.urlLookup.add(url, mapping);
			}
			...
			this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
		}
	}
}

2、处理请求

SpringMVC利用请求url获取目标handler。
在这里插入图片描述

public class DispatcherServlet extends FrameworkServlet {
	@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				// 围绕 RequestMappingHandlerMapping 展开
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}
}
public abstract class AbstractHandlerMethodMapping<T>  implements InitializingBean {
	private final MappingRegistry mappingRegistry = new MappingRegistry();
	@Override
	protected HandlerMethod getHandlerInternal(HttpServletRequest request){
		// 获取requestUri
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		// 从 mappingRegistry 获取目标handler
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) {
		List<Match> matches = new ArrayList<>();
		// 利用 lookupPath 从MappingRegistry相关属性中获取目标handler之T
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		// 通过章节1得知,返回的类为 RequestMappingInfo,即@RequestMapping注解的相关属性
		if (directPathMatches != null) {
			//分析请求
			addMatchingMappings(directPathMatches, matches, request);
		}
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}
		// 如果局部matches中元素为空,说明请求头校验失败
		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);
			Match bestMatch = matches.get(0);
			...
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}else {
			//最终此处抛出相关的异常
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}
}

请求头相关参数校验失败抛出的异常包括:HttpRequestMethodNotSupportedExceptionHttpMediaTypeNotSupportedExceptionHttpMediaTypeNotAcceptableExceptionUnsatisfiedServletRequestParameterException

但是这些类型的异常好像不能被全局拦截器拦截处理。

2.1.解析请求头相关属性

核心就是校验请求request中相关属性跟RequestMappingInfo中属性是否匹配。

public abstract class AbstractHandlerMethodMapping<T>  implements InitializingBean {

	private void addMatchingMappings(Collection mappings, List matches, HttpServletRequest request) {
		for (T mapping : mappings) {
			T match = getMatchingMapping(mapping, request);
			if (match != null) {//正常请求校验都通过,最终返回重新包装后的RequestMappingInfo
				matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
			}
		}
	}
}
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping {
	@Override
	protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
		return info.getMatchingCondition(request);
	}
}

2.1.1、RequestMappingInfo

该类中的相关属性是对 注解@RequestMapping之字段属性的封装。

public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
	public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
		//请求方式: 校验Method属性,即是否为post or get 等相对应的请求方式
		RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
		if (methods == null) {
			return null;
		}
		//请求参数
		ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
		if (params == null) {
			return null;
		}
		//consumer参数:请求头中contentType是否一致。
		HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
		if (headers == null) {
			return null;
		}
		//producer参数:请求头中Accept是否一致
		ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
		if (consumes == null) {
			return null;
		}
		ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
		if (produces == null) {
			return null;
		}
		PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
		if (patterns == null) {
			return null;
		}
		RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
		if (custom == null) {
			return null;
		}
		return new RequestMappingInfo(this.name, patterns,
				methods, params, headers, consumes, produces, custom.getCondition());
	}
}

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

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

相关文章

Java入门篇 之 数据类型(简单介绍)

博主回归学习状态的第三篇文章&#xff0c;希望对大家有所帮助 今日份励志文案:你若决定灿烂&#xff0c;山无遮&#xff0c;海无拦 加油&#xff01; Java中一共存在2种数据类型 1 . 基本数据类型,基本数据类型四种和八种之说(具体看下图) 四种说的是&#xff0c;整数型&…

vscode打开settings.json方法

cmd shift p&#xff0c;输入setting Open Workspace Settings 也会打开UI设置界面&#xff1b; Open User Settings (JSON) 会打开用户设置 settings.json 文件&#xff1b; Open Workspace Settings (JSON) 会打开工作区设置 settings.json 文件 vscode存在两种设置 sett…

损失函数和目标函数|知识补充

这张图中&#xff0c;横坐标size表示房屋的大小&#xff0c;纵坐标price表示房屋的价格&#xff0c;现在需要建立模型来表示两者之间的关系。 对于给定的输入x&#xff0c;模型会有一个输出f(x)&#xff0c;用一个函数来度量拟合的程度&#xff0c;也就是真实值和预测值之间的…

前端工程化面试题及答案【集合】

前言&#xff1a; 欢迎浏览和关注本专栏《 前端就业宝典 》&#xff0c; 不管是扭螺丝还是造火箭&#xff0c; 多学点知识总没错。 这个专栏是扭螺丝之上要造火箭级别的知识&#xff0c;会给前端工作学习的小伙伴带来意想不到的帮助。 本专栏将前端知识拆整为零&#xff0c;主要…

工业相机常见的工作模式、触发方式

参考&#xff1a;机器视觉——工业相机的触发应用(1) - 知乎 工业相机常见的工作模式一般分为&#xff1a; 触发模式连续模式同步模式授时同步模式 触发模式&#xff1a;相机收到外部的触发命令后&#xff0c;开始按照约定时长进行曝光&#xff0c;曝光结束后输出一帧图像。…

子集生成算法:给定一个集合,枚举所有可能的子集

给定一个集合&#xff0c;枚举所有可能的子集。 &#xff08;为简单起见&#xff0c;本文讨论的集合中没有重复元素&#xff09; 1、方法一&#xff1a;增量构造法 第一种思路是一次选出一个元素放到集合中&#xff0c;程序如下&#xff1a; void print_subset(int n, int …

C++系列之list的模拟实现

&#x1f497; &#x1f497; 博客:小怡同学 &#x1f497; &#x1f497; 个人简介:编程小萌新 &#x1f497; &#x1f497; 如果博客对大家有用的话&#xff0c;请点赞关注再收藏 &#x1f31e; list的节点类 template struct list_Node { public: list_Node* _prev; list_…

Tomcat服务部署和优化

目录 一、Tomcat&#xff1a; 1、Tomcat作用&#xff1a; 2、Tomcat的核心组件&#xff1a; 3、servlet作用&#xff1a; 4、Tomcat的核心功能&#xff1a; 二、tomcat配置 一、Tomcat&#xff1a; 是一个开源的web应用服务器&#xff0c;nginx主要处理静态页面&#xff…

不再受害:如何预防和应对.mallab勒索病毒攻击

导言&#xff1a; 我们的数据成了我们的珍宝&#xff0c;但也成了黑客们追逐的目标。其中&#xff0c;.mallab勒索病毒就是一个充满阴谋和神秘的数字威胁&#xff0c;它采用高度复杂的方法将您的数据锁在数字牢笼中。本文91数据恢复将深入探讨.mallab勒索病毒的起源、工作方式…

【RabbitMQ 实战】12 镜像队列

一、镜像队列的概念 RabbitMQ的镜像队列是将消息副本存储在一组节点上&#xff0c;以提高可用性和可靠性。镜像队列将队列中的消息复制到一个或多个其他节点上&#xff0c;并使这些节点上的队列保持同步。当一个节点失败时&#xff0c;其他节点上的队列不受影响&#xff0c;因…

视频转换器WinX HD Video Converter mac中文特点介绍

WinX HD Video Converter mac是一款功能强大的视频转换器&#xff0c;它可以将各种不同格式的视频文件转换为其他视频格式&#xff0c;以便用户在各种设备上进行播放。WinX HD Video Converter是一个功能强大、易于使用的视频转换器&#xff0c;适用于各种类型的用户&#xff0…

可图性判断(图论)

如图所示&#xff1a; 1.去arr[i]首元素&#xff0c; 后面arr[i]个元素减一 2.排序&#xff0c;以此类推 3.最后如果出现负数则不可图 4.最后元素为0&#xff0c;则可图 问题 L: Degree Sequence of Graph G代码如下&#xff1a;

C#版字节跳动SDK - SKIT.FlurlHttpClient.ByteDance

前言 在我们日常开发工作中对接第三方开放平台&#xff0c;找一款封装完善且全面的SDK能够大大的简化我们的开发难度和提高工作效率。今天给大家推荐一款C#开源、功能完善的字节跳动SDK&#xff1a;SKIT.FlurlHttpClient.ByteDance。 项目官方介绍 可能是全网唯一的 C# 版字节…

基于nodejs+vue全国公考岗位及报考人数分析

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

iOS的应用生命周期

在iOS的原生开发中&#xff0c;我们需要特别关注两个东西&#xff1a;AppDelegate和ViewController。我们主要的编码工作就是在AppDelegate和ViewControlle这两个类中进行的。它们的类图如下图所示&#xff1a; AppDelegate是应用程序委托对象&#xff0c;它继承了UIResponder类…

抖音上怎么挂小程序?制作小程序挂载抖音视频

公司企业商家现在已经把抖音作为营销的渠道之一&#xff0c;目前抖音支持短视频挂载小程序&#xff0c;可方便做营销。以下给大家分享这一操作流程。 一、申请自主挂载能力 首先需要在抖音开放平台官网注册一个抖音小程序账号&#xff0c;然后申请短视频自主挂载能力。 二、搭…

Kubernetes技术与架构-存储 2

在Kubernetes集群中&#xff0c;一块持久化存储空间是可以被回收再利用&#xff0c;简称PV&#xff0c;即PersistentVolume&#xff0c;Pod实例需要使用PV的时候&#xff0c;可以使用PVC定义申请PV存储资源&#xff0c;PVC是PersistentVolumeClaim的简称&#xff0c;PV的申请分…

Git(SourceTree)变基操作使用

文章目录 一、变基的使用场景二、Source Tree上的变基操作1. 准备两个分支dev1和master2. 切换到dev1中&#xff0c;并选中master中提交的代码3. 鼠标右键&#xff0c;选择变基&#xff0c;弹出对话框选择确定。 变基就是rebase操作 一、变基的使用场景 假设分支a和分支b 在分…

Day12力扣打卡

打卡记录 找出满足差值条件的下标 II&#xff08;双指针维护最大最小&#xff09; 链接 采用双指针保留间隔 indexDifference 进行遍历&#xff0c;求出慢指针对应一路遍历过来的最大值和最小值。 class Solution { public:vector<int> findIndices(vector<int>…

【排序】js简单实现前端数组排序,多字段数组对象排序,字符串排序,数字排序等

数组对象排序&#xff08;多字段排序&#xff09; 排序前&#xff1a; 排序后&#xff1a; data() {return {list: [{ks: 外科,child_ks: 泌尿外科,xz: 外科一组,doctor: 小明,num: 18,num2: 19,num3: 20},{ks: 中医科,child_ks: 中医男科,xz: 外科一组,doctor: 小红,num: …