Spring MVC之HandlerMapping

1. 前言

Spring MVC将请求处理器定义为handler,因为handler可以以很多形式存在,所以Spring并没有限制handler的类型,用Object来表示。然后又因为这个原因,Spring MVC针对不同的handler设计了不同的HandlerAdapter来协调handler处理请求。
那么,Spring是怎么根据请求Request查找到对应的处理器handler的呢?

2. HandlerMapping

根据请求Request查找到对应的处理器handler,这个职责Spring交给了HandlerMapping处理。HandlerMapping是处理器映射器,它的职责就是查找请求对应的处理器。

public interface HandlerMapping {@NullableHandlerExecutionChain getHandler(HttpServletRequest request);
}

HandlerMapping接口定义足够简单,只有一个方法getHandler(),根据请求request对象,查找到可以处理请求的处理器handler。又因为handler是可以支持自定义拦截器HandlerInterceptor的,所以Spring将handler和一堆拦截器统一封装为HandlerExecutionChain对象。

3. HandlerExecutionChain

因为除了handler本身处理业务逻辑之外,Spring MVC还支持给handler注册拦截器HandlerInterceptor,所以Spring MVC把它们封装成了HandlerExecutionChain对象。

public class HandlerExecutionChain {// 目标处理器private final Object handler;// 一堆拦截器@Nullableprivate HandlerInterceptor[] interceptors;@Nullableprivate List<HandlerInterceptor> interceptorList;// 拦截器索引private int interceptorIndex = -1;
}

HandlerExecutionChain对象通过HandlerMapping#getHandler()方法构建:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 子类获取HandlerObject handler = getHandlerInternal(request);if (handler == null) {// 没有,尝试用默认处理器handler = getDefaultHandler();}if (handler == null) {return null;}// 如果返回的是字符串,则认定为它的处理器的beanName,去容器加载handlerif (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}// 基于handler和一堆拦截器 构建HandlerExecutionChainHandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);if (logger.isTraceEnabled()) {logger.trace("Mapped to " + handler);} else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {logger.debug("Mapped to " + executionChain.getHandler());}if (CorsUtils.isCorsRequest(request)) {CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;
}

构建HandlerExecutionChain的流程是:

  1. 调用子类自定义获取handler的逻辑,如果没有尝试使用默认处理器。
  2. 如果handler是字符串类型,则认为它是beanName,通过容器获取。
  3. 基于handler和一堆拦截器构建HandlerExecutionChain。

4. 获取Handler

父类主要是基于子类返回的handler和拦截器构建HandlerExecutionChain,子类获取handler的逻辑才是核心,方法是AbstractHandlerMapping#getHandlerInternal()。HandlerMethod是我们常用的handler,所以我们重点看AbstractHandlerMethodMapping类。

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {// 查找HandlerMethod的路径 即解析请求URIString lookupPath = getUrlPathHelper().getLookupPathForRequest(request);this.mappingRegistry.acquireReadLock();// 加读锁try {// 从MappingRegistry中查找HandlerHandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);} finally {this.mappingRegistry.releaseReadLock();}
}

主要做了两件事:

  1. 解析请求的URI,作为查找handler的依据。
  2. 通过MappingRegistry查找handler。

MappingRegistry是映射器注册表,Spring MVC启动时会将扫描到的handler注册到MappingRegistry,查找handler的代码是:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<>();// URI直接路径匹配List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {addMatchingMappings(directPathMatches, matches, request);}if (matches.isEmpty()) {// 没有直接匹配到,只能模糊匹配addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}if (!matches.isEmpty()) {// 匹配到多个,按优先级排序Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));matches.sort(comparator);Match bestMatch = matches.get(0);if (matches.size() > 1) {if (logger.isTraceEnabled()) {logger.trace(matches.size() + " matching mappings: " + matches);}if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;}Match secondBestMatch = matches.get(1);if (comparator.compare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();Method m2 = secondBestMatch.handlerMethod.getMethod();String uri = request.getRequestURI();throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");}}// 把匹配到的HandlerMethod写入request 最佳匹配handlerrequest.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);handleMatch(bestMatch.mapping, lookupPath, request);return bestMatch.handlerMethod;} else {return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}
}

步骤:

  1. 先按URI直接路径匹配
  2. URI没有直接匹配到,只能模糊匹配
  3. 如果匹配到多个,按优先级排序
  4. 把匹配到的HandlerMethod写入request属性,记为“最佳匹配handler”

5. HandlerMapping怎么来的

HandlerMapping可以有多个,通过属性handlerMappings保存,它是一个List。查找handler的过程也很简单,遍历所有HandlerMapping挨个查找,只要找到了handler就直接返回。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;
}

那HandlerMapping是怎么来的呢???
Spring MVC通过变量detectAllHandlerMappings来控制是否要自动检测所有HandlerMapping,默认是true。

  1. 如果detectAllHandlerMappings为true,则加载容器内所有的HandlerMapping,否则只加载beanName为"handlerMapping"的单个HandlerMapping。
  2. 如果容器内没有可用的HandlerMapping,Spring还有一个兜底方案,就是通过getDefaultStrategies()加载默认的handlerMappings。

默认的handlerMappings是通过classPath下名为DispatcherServlet.properties的配置文件来加载的,如下是Spring MVC的默认配置:

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

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

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

相关文章

基于ElementUI封装的下拉树选择可搜索单选多选清空功能

效果&#xff1a; 组件代码 /*** 树形下拉选择组件&#xff0c;下拉框展示树形结构&#xff0c;提供选择某节点功能&#xff0c;方便其他模块调用* author wy* date 2024-01-03 * 调用示例&#xff1a;* <tree-select * :height"400" // 下拉框中树形高度* …

【数据结构】二叉树的概念及堆

前言 我们已经学过了顺序表、链表、栈和队列这些属于线性结构的数据结构&#xff0c;那么下面我们就要学习我们第一个非线性结构&#xff0c;非线性结构又有哪些值得我们使用的呢&#xff1f;那么接下来我们就将谈谈树的概念了。 1.树的概念与结构 1.1树的概念 树是一种非线性…

python数据可视化之折线图案例讲解

学习完python基础知识点&#xff0c;终于来到了新的模块——数据可视化。 我理解的数据可视化是对大量的数据进行分析以更直观的形式展现出来。 今天我们用python数据可视化来实现一个2023年三大购物平台销售额比重的折线图。 准备工作&#xff1a;我们需要下载用于生成图表的第…

2024苹果Mac电脑免费文件数据恢复软件EasyRecovery

EasyRecovery是一个操作安全、价格便宜、用户自主操作的非破坏性的只读应用程序&#xff0c;它不会往源驱上写任何东西&#xff0c;也不会对源驱做任何改变&#xff01;EasyRecovery是一个操作安全、价格便宜、用户自主操作的非破坏性的只读应用程序&#xff0c;它不会往源驱上…

Android 15即将到来,或将推出5大新功能特性

Android15 OneUI电池优化 三星最近完成了对其所有设备的稳定版 One UI 6.0 更新的推出&#xff0c;引起了用户的极大兴奋。据新出现的互联网统计数据显示&#xff0c;即将发布的基于 Android 15 的 One UI 7 将通过优化电池和功耗来重新定义用户体验&#xff0c;这是一项具有突…

【开源项目】WPF 扩展组件 -- Com.Gitusme.Net.Extensiones.Wpf

一、项目简介 Com.Gitusme.Net.Extensiones.Wpf 是一款 Wpf 扩展组件。基于.Net Core 3.1 开发&#xff0c;当前最新 1.0.1 版本。包含 核心扩展库&#xff08;Com.Gitusme.Net.Extensiones.Core&#xff09;、视频渲染&#xff08;Com.Gitusme.Media.Video&#xff09;、串口…

基于多反应堆的高并发服务器【C/C++/Reactor】(中)线程池的启动和从线程池中取出一个反应堆实例

一、线程池的启动 &#xff08;主线程&#xff09; // 启动线程池 &#xff08;主线程&#xff09; void threadPoolRun(struct ThreadPool* pool) {/*线程池被创建出来之后&#xff0c;接下来就需要让线程池运行起来&#xff0c;其实就是让线程池里的若干个子线程运行起来*//…

小微企业在银行信贷相关产品和机器学习建模案例_论文科研_企业调研

各银行小微企业贷款业务 互联网的时代&#xff0c;大量新信息技术的涌现和网络的无处不在&#xff0c;想要抢占这片金融天地&#xff0c;必须重视小微金融业务&#xff0c;小微企业是一直具有重大潜力的客户&#xff0c;商业银行、消金公司发展小微信贷业务可以拓宽自身客户群…

java公交系统毕业论文

公交系统毕业论文 毕业设计题目:基于Java的公交车查询系统的设计与实现 年 月 日 毕 业 设 计 中 文 摘 要 随着中国经济的快速发展&#xff0c;我国交通运输业也在不断的优化发展。公交车的站点和线路的增多也给人们的出行带来了极大的不便&#xff0c;特别是针对一些旅客…

系统学英语 — 音标音节 — 能读就能写

目录 文章目录 目录概览12 个单元音8 个双元音28 个辅音音节 概览 12 个单元音 序号发音音标助记字母组合备注1拖长音 前腔[i:]eate、ea、ee、ie2短促音 前腔[i]bige、i、y3拖长音 后腔[a:]aska、ar4短促音 中腔[ʌ]runu、o、ou、oo5拖长音 中腔[ə:]earlyer、ir、or、ur…

Python群论:置换和置换群

文章目录 简介置换的复合置换群 简介 所谓置换&#xff0c;简单地说就是交换两个元素的位置。例如&#xff0c;给定一组元素 a 0 , a 1 , a 2 a_0, a_1, a_2 a0​,a1​,a2​&#xff0c;那么通过置换之后&#xff0c;这组元素可以变成 a 0 , a 2 , a 1 a_0, a_2, a_1 a0​,a2​…

SpringBoot配置多数据源

SpringBoot配置多数据源 前置条件数据库配置pom.xmlapplication.yml代码Two.javaTwoMapper.javaTwoMapper.xmlTwoService.javaTwoServiceImpl.javaTwoController.java效果前置条件 项目已经集成了mysql单数据源 数据库 数据库demo CREATE TABLE `test` (`id` int(11) NOT NUL…

LLM漫谈(二)| QAnything支持任意格式文件或数据库的本地知识库问答系统

一、QAnything介绍 QAnything (Question and Answer based on Anything) 是致力于支持任意格式文件或数据库的本地知识库问答系统&#xff0c;可断网安装使用。 您的任何格式的本地文件都可以往里扔&#xff0c;即可获得准确、快速、靠谱的问答体验。 目前已支持格式: PDF&…

基于商品列表的拖拽排序后端实现

目录 一&#xff1a;实现思路 二&#xff1a;实现步骤 二&#xff1a;实现代码 三&#xff1a;注意点 一&#xff1a;实现思路 后台实现拖拽排序通常需要与前端进行配合&#xff0c;对商品的列表拖拽排序&#xff0c;前端需要告诉后端拖拽的元素和拖动的位置。 这里我们假…

MySQL第四战:视图以及常见面试题(上)

目录 目录&#xff1a; 一.视图 1.介绍什么是视图 2.视图的语法 语法讲解 实例操作 二.MySQL面试题 1.SQL脚本 2.面试题实战 三.思维导图 目录&#xff1a; 随着数字化时代的飞速发展&#xff0c;数据库技术&#xff0c;特别是MySQL&#xff0c;已经成为IT领域中不可…

Java-网络爬虫(二)

文章目录 前言一、WebMagic二、使用步骤1. 搭建 Maven 项目2. 引入依赖 三、入门案例四、核心对象&组件1. 核心对象SipderRequestSitePageResultItemsHtml&#xff08;Selectable&#xff09; 2. 四大组件DownloaderPageProcessorSchedulerPipeline 上篇&#xff1a;Java-网…

使用Enterprise Architect绘制架构图

如何使用Enterprise Architect绘制架构图 之前没有使用过Enterprise Architect软件绘制&#xff0c;目前由于工作需求&#xff0c;需要使用Enterprise Architect绘制一些架构图&#xff0c;现在只使用Enterprise Architect绘制过简单的Flow Chart&#xff0c;想请教一下大神们…

文章解读与仿真程序复现思路——中国电机工程学报EI\CSCD\北大核心《考虑系统调峰需求与光热电站收益平衡的储热容量优化配置》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主的专栏栏目《论文与完整程序》 这个标题表明研究的主题涉及到光热电站系统中的储热容量优化配置&#xff0c;而优化的目标是在系统中实现调峰需求并平衡光热电站的收益。让我们逐步解读这…

学习使用layPage, 多功能JS分页组件/插件的方法

学习使用layPage, 多功能JS分页组件/插件的方法 效果图分页代码 效果图 点击查看链接 分页代码 <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>Layui</title><meta name"renderer" content"we…

数字系统课程设计与VHDL报告

获“优”&#xff0c;含实验结果视频、代码、报告&#xff0c;99.99%原创&#xff0c;配置环境太复杂不包跑通&#xff0c;要的私信。