源码解析SpringMVC之RequestMapping注解原理
1、启动初始化
核心:得到应用上下文中存在的全部bean后依次遍历,分析每一个目标handler & 目标方法存在的注解@RequestMapping,将其相关属性封装为实例RequestMappingInfo。最终将 uri & handler 之间的映射关系维护在类AbstractHandlerMethodMapping中的内部类RequestMappingInfo中。
利用RequestMappingHandlerMapping之InitializingBean接口特性来完成请求 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()) {//从应用上下文中获取全部的beanprocessCandidateBean(beanName);}}protected void processCandidateBean(String beanName) {Class<?> beanType = obtainApplicationContext().getType(beanName);// 判断是否为Handler的条件为:是否存在注解Controller 或者 RequestMappingif (beanType != null && isHandler(beanType)) {detectHandlerMethods(beanName);}}protected void detectHandlerMethods(Object handler) {//handler为String类型的beanNameClass<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {Class<?> userType = ClassUtils.getUserClass(handlerType);// 集合methods其key:反射中Method类。value:RequestMappingInfoMap<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {// 调用具体的实现类,例如RequestMappingHandlerMappingreturn 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@Nullableprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {//将目标方法存在的@RequestMapping注解相关属性封装为RequestMappingInfoRequestMappingInfo info = createRequestMappingInfo(method);if (info != null) {//如果目标handler也存在@RequestMapping注解,则也封装为RequestMappingInfoRequestMappingInfo 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注解相关属性,最终封装为RequestMappingInfoRequestMappingInfo.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:HandlerMethodprivate 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的beanNameHandlerMethod 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 {@Nullableprotected 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();@Overrideprotected HandlerMethod getHandlerInternal(HttpServletRequest request){// 获取requestUriString lookupPath = getUrlPathHelper().getLookupPathForRequest(request);// 从 mappingRegistry 获取目标handlerHandlerMethod 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之TList<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);}}
}
请求头相关参数校验失败抛出的异常包括:HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException、UnsatisfiedServletRequestParameterException。
但是这些类型的异常好像不能被全局拦截器拦截处理。
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) {//正常请求校验都通过,最终返回重新包装后的RequestMappingInfomatches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));}}}
}
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping {@Overrideprotected 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());}
}
相关文章:
源码解析SpringMVC之RequestMapping注解原理
1、启动初始化 核心:得到应用上下文中存在的全部bean后依次遍历,分析每一个目标handler & 目标方法存在的注解RequestMapping,将其相关属性封装为实例RequestMappingInfo。最终将 uri & handler 之间的映射关系维护在类AbstractHand…...
biocParallel学习
我好像做了一个愚蠢的测试 rm(listls()) suppressPackageStartupMessages({library(SingleCellExperiment)library(scMerge)library(scater)library(Matrix) })setwd("/Users/yxk/Desktop/test/R_parallel/") load("./data/exprsMat.RData") load(".…...
AWTK实现汽车仪表Cluster/DashBoard嵌入式GUI开发(六):一个AWTK工程
一个AWTK工程基于C/C++编写,可以分为如下几步: 结合下图,看懂启动的部分。一般一个AWTK工程,需要实现哪些部分,就是其中开始之后白色的部分,比如调用main函数和gui_app_start时会做一些操作,比如asset_init和application_init时要做一些设置,还有退出的函数application…...
MySQL主从复制(基于binlog日志方式)
目录 一、什么是主从复制?二、主从复制原理、存在问题和解决方法2.1.主从复制原理2.2.主从复制存在的问题以及解决办法2.3.主从复制的同步模型2.4.拓展—Mysql并行复制 三、主从复制之基于binlog日志方式3.1.bin-log日志简介3.2.bin-log的使用3.2.1.开启binlog3.2.2…...
计算机网络【CN】介质访问控制
信道划分介质访问控制 FDMTDMWDMCDM【掌握eg即可】 随机介质访问控制 CSMA 1-坚持CSMA 非坚持CSMA p-坚持CSMA 空闲时 立即发送数据 立即发送数据 以概率P发送数据,以概率1-p推迟到下一个时隙 忙碌时 继续坚持侦听 放弃侦听,等待一个随机的时…...
CDR和AI哪个软件更好用?
设计软件市场中,CorelDRAW和Adobe Illustrator(简称AI)无疑是两大重量级选手。它们各自拥有庞大的用户群和丰富的功能,但究竟哪一个更好用?本文将从多个角度出发,对这两款软件进行全面而深入的比较…...
保姆级认识AVL树【C++】(精讲:AVL Insert)
目录 前言 一,概念 二,定义 三,insert 1. 插入情况 情况一: 情况二: 情况三: 2. 旋转方法 法一:左单旋法 法二:右单旋法 法三:先左后右双旋法 法四…...
pinia中使用reactive声明变量,子页面使用时,值未改变,即不是响应式的(解决方法)
reactive赋值无效!reactive 不要直接data赋值!!!会丢失响应式的,只能通过obj.属性 属性值赋值 方法一. pinia中直接使用ref定义变量即可 export const useUserStoredefineStore(user,()>{let loginUserreactive({…...
基于springboot零食商城管理系统
功能如图所示 摘要 这基于Spring Boot的零食商城管理系统提供了强大的购物车和订单管理功能。用户可以在系统中浏览零食产品,并将它们添加到购物车中。购物车可以保存用户的选购商品,允许随时查看已选择的商品和它们的数量。一旦用户满意,他们…...
C++程序练习
定义一个类CheckPath,它由两个public方法组成: 1) checkPath:检查传入的字符串指定的路径是否存在,存在返回true,否则返回false。 2) createFilePath:根据传入的字符串指定的路径&…...
Golang 继承
在面向对象的编程语言中,继承是一种重要的机制,它允许子类继承父类的属性和方法。然而,Go语言在设计时没有直接支持传统意义上的继承,而是提供了一种更为灵活和简洁的方式来实现类似的功能。本文将探讨Golang中实现继承的方法和最…...
棋盘格测距-单目相机(OpenCV/C++)
一、文章内容简述: 1’ 通过cv::findChessboardCorners寻找棋盘格角点 2‘ 用cv::solvePnP计算旋转向量rvec和平移向量tvec 3’ 通过公式计算相机到棋盘格的距离 float distance sqrt(tvec.at<double>(0,0) * tvec.at<double>(0,0) tvec.at<do…...
031-从零搭建微服务-监控中心(一)
写在最前 如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。 源码地址(后端):mingyue: 🎉 基于 Spring Boot、Spring Cloud & Alibaba 的分布式微服务架构基础服务中心 源…...
vue中使用xlsx插件导出多sheet excel实现方法
安装xlsx,一定要注意版本: npm i xlsx0.17.0 -S package.json: {"name": "hello-world","version": "0.1.0","private": true,"scripts": {"serve": "vue-c…...
Linux - 进程的优先级 和 如何使用优先级调度进程
理解linux 当中如何做到 把一个PCB 放到多个 数据结构当中 在Linux 当中,一个进程的 PCB 不会仅仅值存在一个 数据结构当中,他既可以在 某一个队列当中,又可以在 一个 多叉树当中。 队列比如 cpu 的 运行队列,键盘的阻塞队列等等…...
支持控件drag和click
在 MouseDown 事件触发 DoDragDrop 拖拽操作时,Click 事件通常无效,因为 DoDragDrop 方法会捕获鼠标事件并等待拖拽操作完成。 有一个简单地思路解决这个问题 当MouseDow时,触发定时器,延迟100s定时器到时后,进入dra…...
AIR101 LuatOS LVGL 显示多个标签例程
屏幕资料 AIR101与屏幕连接 PC端仿真环境合宙官方PC端版本环境搭建教程 PC电脑仿真 -- sys库是标配 _G.sys require("sys") sys.taskInit(function()local cnt0lvgl.init(480,320)--lvgl初始化local cont lvgl.cont_create(nil, nil);-- lvgl.cont_set_fit(cont, …...
Istio实战(七)- Bookinfo 部署
1. Istio Bookinfo示例 1.1 部署Bookinfo # kubectl apply -f /apps/istio/samples/bookinfo/platform/kube/bookinfo.yaml -n hr1.2 确认Bookinfo已经部署正常 先确认以下pod和service已经被正确创建 # kubectl get pods -n hr NAME READY …...
出差学小白知识No5:|Ubuntu上关联GitLab账号并下载项目(ssh key配置)
1 注冊自己的gitlab账户 有手就行 2 ubuntu安装git ,并查看版本 sudo apt-get install git git --version 3 vim ~/.ssh/config Host gitlab.example.com User your_username Port 22 IdentityFile ~/.ssh/id_rsa PreferredAuthentications publickey 替换gitl…...
FL Studio21.2中文版多少钱?值得下载吗
水果,全称Fruity Loop Studio,简称FL Studio。是一款全能的音乐制作软件,经过二十多年的演化更迭,其各项功能非常的先进。其开创性的Pat\song模式,也为初学者的学习提供了便利。那么水果音乐制作软件需要多少钱呢&…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
