当前位置: 首页 > news >正文

SpringMVC源码深度解析(中)

        接上一遍博客《SpringMVC源码深度解析(上)》继续聊。最后聊到了SpringMVC的九大组建的初始化,以 HandlerMapping为例,SpringMVC提供了三个实现了,分别是:BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、RouterFunctionMapping,其中最常用的就是 RequestMappingHandlerMapping,与@RequestMapping注解相关,以它为例,先看看类的继承图:

130df131cd6b4f899649c07e59227094.png

        可知该类实现了InitializingBean接口,这是Spring的扩展点,Bean对象在初始化的时候,会调用 RequestMappingHandlerMapping#afterPropertiesSet()方法。如果使用@EnableWebMvc注解或者手动往Spring容器中注入 RequestMappingHandlerMapping对象,Web容器在调用refresh()方法的过程中,会调用afterPropertiesSet()方法,如果不是通过以上两种方式的话,由上一篇博客 《SpringMVC源码深度解析(上)》可知,会读取dispatcherservlet.properties文件,获取默认的HandlerMapping的实现类,但是由于在前面已经调用了Web容器的refresh()方法,完成了容器的刷新,因此,这里需要特殊处理,代码如下:

c667fc6d4dbc4b6986c31a798e9878b7.png

aa6fd44d8300406baa73b21734bdf5c5.png

d61f5b8f4b4c4203ab6de72f00766b93.png

        可知,这里会显示的调用AutowireCapableBeanFactory#createBean()方法,才会经历Bean的实例化与初始化,才会调用 InitializingBean#afterPropertiesSet()方法。我专门提到这个方法,也侧面说明这个方法很重要,看看这个方法,代码如下:

d9b84c5e710d412a8c9a08fd41519fb5.png

0343cd486c47429a87cac516188b1c36.png

47addf5813f3427a95352d660b971e60.png

a7db417aae9c4d18acf3be0d92038314.png

        重点看看AbstractHandlerMethodMapping#getMappingForMethod()方法,代码如下:

0a86ddf09067421e9d366917bcef95f6.png

459a2689b116474bb763f5840e3f41c4.png

        再看看AbstractHandlerMethodMapping#registerHandlerMethod()方法,代码如下:

07e2baec26a747e889467b542de4de64.png

eedb5f6669b049aea481e17185a7e9b1.png

030b3f5080714aefafa2b31f8202edc7.png

        到这里为止就可以知道:RequestMappingHandlerMapping在初始化的时候,就会获取到所有被@Controller/@RequestMapping注解修饰的类,并且得到该类中所有被@Requestmapping注解修饰的方法,每个被@RequestMapping注解修饰的方法,最终生成一个对应的HandlerMethod对象(存储被@Controller/@RequestMapping注解修饰的对象和被@RequestMapping注解修饰的方法),并存储在内部类RequestMappingHandlerMapping.MappingRegistry的registry(Map)属性中,key为RequestMappingInfo对象,value为MappingRegistration对象(存储HandlerMethod对象)。

        聊完了RequestMappingHandlerMapping在初始化,再聊聊SpringMVC是如何处理请求。其实就是要搞清楚DispatcherServlet的原理,它是一个Servlet,在《SpringMVC源码深度解析(上)》中也说到了,创建了DispatcherServlet对象后,会添加到Servlet容器中。如果有请求来了,会解析url并匹配,如果是符合DispatcherServlet的匹配路径,则调用 GenericServlet#service()方法,这是一个抽象方法,因此实际调用的是HttpServlet#service()方法,代码如下:

df8ce42de145419eae7ed5cbfdb74626.png

5786dc6ea931490291ea4d35fb6e9f95.png

        以POST请求为例,则调用的是HttpServlet#doPost()方法,代码如下:

7cf0bb1a7b8840b5b477cb82ac756d03.png

        其实对于DispatcherServlet来讲,不管是哪种请求,最终都是调用FrameworkServlet#processRequest()方法,代码如下:

2c8ba08a6bfe4c6aad18dbd6e6752f54.png

8cf4766a9c11457e879eabe7a6764de8.png

d7ee9911e4954b109147fcd7f740f611.png

        DispatcherServlet#doDispatch()方法是处理请求的核心,会重点讲,代码如下:

0fae340fd041460c9a9b609b45e108cc.png

47bf6a5d9a744b54b4c80b8fa2e8d75e.png

fb8f78df6b54462a9b015115fe7aa275.png

95b168e8df68427682f2ee2c5dca5c1f.png

87dcea8e204a466db070d6683d1a286b.png

        在AbstractHandlerMethodMapping#lookupHandlerMethod()方法中,主要是进行路径的匹配,由于在前面RequestMappingInfoHandlerMapping初始化的时候,已经存储好了所有的匹配路径及其HandlerMethod,存放在 RequestMappingInfoHandlerMapping.MappingRegistry的pathLookup属性中,因此如果满足匹配要求,是可以获取到HandlerMethod对象的,这块就说了,涉及到匹配逻辑,比较复杂,感兴趣可以自己研究。回到AbstractHandlerMapping#getHandler()方法中,看看AbstractHandlerMapping#getHandlerExecutionChain()方法,代码如下:

d35f0f23aaf14c20a9f59202bb3a0c76.png

        可知这这个方法中,就是创建HandlerExecutionChain对象,并将HandlerInterceptor设置到HandlerExecutionChain对象的interceptorList属性中。再回到DispatcherServlet#doDispatch()方法中,代码如下:

610000c0dbe24c8386a65301a37b6423.png

fe99698349434210ac4f7a7295c0d21f.png

bb314fe87516442a89a90ad8a16a539a.png

3a0cc90b5ffd41b9b9933816ee0ed98e.png

        因此返回的HandlerAdapter对象是RequestMappingHandlerAdapter,这里当用到的是适配器设计模式,为什么要做适配呢?还记得之前HandlerMapping由三个实现类的,实际上处理的是三种不同的Controller,因此需要有对应的适配器处理不同的情况,看看HandlerAdapter#handle()方法就知道了,具体如下:

b865b6a9f7a54848870ae985a1493218.png

c10ce604743f434b917aaaf9d75f6b54.png

b93ca25bc2ab4f968b9d99e00eec9fbd.png

a041b5b6706e43f28f32d336b3f3b22a.png

        主要是处理这三种情况:

                ① 一种是被@Controller/@RestController/@RequestMapping注解修饰的类;

                ② 实现了HttpRequestHandler接口的类;

                ③ 实现了Controller接口的类;

        这三种情况的类,都属于Controller,因此需要不同的适配器处理,我主要讲①,剩下的两种,有兴趣可以试试。回到DispatcherServlet#doDispatch()方法继续往下看,代码如下:

2e281ea548cd4333a99090581cb7fab7.png

        执行拦截器的前置方法,就是遍历HandlerExecutionChain的interceptorList属性,执行HandlerInterceptor#preHandle()方法,代码如下:

327a2e5d938146bda0f30f07763642de.png

        执行拦截器的后置方法,也是如此,代码如下:

b4120edb01b3438f819bd9a0bbe4e455.png

        当然,如果执行某个拦截器的前置方法,返回的是 false,则直接return,不往下走了。在执行拦截器的前置方法后,后置方法前,会执行HandlerAdapter#handle()方法,代码如下:

1cf683bf4e2a4caaa494c89fcc7bf1a3.png

ed04b3f6c8274393b44fe68f7f833b90.png

381a585ef6d645f4a27bac4450870a8c.png

        顺便说一下,RequestMappingHandlerAdapter也实现了InitializingBean接口,因此也会实现afterPropertiesSet()方法,代码如下:

b33c2f17d23c4c83a7dcfbbbd155b31f.png

50903d44e8e5420d8d5b9120cabeaa34.png

795c878f1c7d45e4aa30958c55ec9a59.png

65a8168b433f40f3afabd977391e53bd.png

        这里设置的这些默认处理器,后续会用到,包括参数的解析、绑定、返回值的处理等。

        再看看 RequestMappingHandlerAdapter#invokeAndHandle()方法,这里是执行目标类中目标方法的关键,代码如下:

7949a157ccdf4ee79ca3d2d6d0568842.png

aa8eb9de3082453e950fa17035a0431a.png

6deb2c171efc43bfa8f9874ce0623886.png

6b132bbe69db4c9ba17ceb25fa4835c6.png

322377a70d88464ca2f070c50e91004a.png

        以PathVariableMethodArgumentResolver为例看看,代码如下:

61be92a661604d16b6cd5ea136b24aca.png

        回到InvocableHandlerMethod#getMethodArgumentValues()方法,看看是如何解析参数的,主要看:HandlerMethodArgumentResolverComposite#resolveArgument()方法,代码如下:

fc07f27ba80b41b694a9be88398c098d.png

        还是以PathVariableMethodArgumentResolver,看看它的resolveArgument()方法,代码如下:

0d4d72b5935044b1b6879d98e5d4e3ba.png

1f6c2c84c11d4c90bb5b0fd367f43680.png

636cbac0db064a599df2bc6735241e6b.png

b954217928e94758807639ac298a5ebe.png

        POST请求为:http://127.0.0.1:9000/hello/sayHello/张三

        79fbc0c3b4c44875bc57d8fef2bec266.png

55dc47e10b5447c89cc13fc3324727a7.png

9edce2b60db54d6d8013991a1ff2fd07.png

                最后将解析获取到的参数值,放入Request的Attribute中。当然,除了这个之外,常用的是在请求体中会放入Json数据,最后参数中接受的是一个对象,SpringMVC是怎么对Json格式的数据进行处理的呢?这里是通过RequestResponseBodyMethodProcessor处理的,代码如下:

e3340f428e054709aae96df8d64e5d10.png

9deb43c41b5e40b58ad954b9f40f1666.png

05a932d23f6b4cc7a62b61b683eab521.png

ab1c99404dde446d86e212f4e0905576.png        一共有八个转换器,这是在哪里添加的呢?后面讲@EnableWebMvc注解的时候会讲到,再看看HttpMessageConverter#read()方法,代码如下:

396e2ad309ff43de8660ee6bf05aec96.png

a293c84178db4b1291674bdd421541be.png

c7a26495dcfa417abd08d57838fe961d.png

4376064fe345496e94c9205c9a19368d.png

1706014d74ae428f831104c18dc401f7.png

6d0a43a5ff654a3eaf47b8193965e015.png

dc0faa3b0bd2406fb49e4736e32fe649.png

aeb2c91f27a846908f8b843703f30e74.png

        到这里可以知道,接收的Java对象,必须有Setter方法,因为这里是直接通过反射调用Setter方法进行赋值的。到这个为止,完成了Json的解析以及对象的赋值,即下图:

81848aa611e54b40880b854ab89f2544.png

        还有一些其他的注解,比如@RequestParam,感兴趣的自己研究。InvocableHandlerMethod#invokeForRequest(),参数处理完了,再调用InvocableHandlerMethod#doInvoke()方法,代码如下:        

182a3cee11144e4390e1a4225fc9ff16.png

847ebdc5a59d43188dc7901314e4017b.png

        反射调用玩目标方法后,会有返回值,也需要处理返回值。回到ServletInvocableHandlerMethod#invokeAndHandle()方法,代码如下:

522d85640998488c985457fd5502d189.png

28367dcba67c4c3195ada2bd10315ea5.png

3dc57b8f87264459afe31ad1e3a25a46.png

由于返回的是对象,因此需要处理,由于HelloController上添加的是@RestController,这个注解相当于@Controller+@ResponseBody,因此最终讲返回的对象反序列化为Json格式的数据,并返回给前端。这里还是需要看RequestResponseBodyMethodProcessor#handleReturnValue()方法,代码如下:

caa845cd98ae401ba99e3d8d4628327e.png

        该方法较长,我只截取重点讲。

61ce4b130bba429fbe0eb9a5fa6dec7d.png

93ab2179205d4a6791a67b162aabf63e.png

5f50a937721d4440a339a15ddf51920d.png

bbae688437cd45bf83340bac5deaf2d0.png

7a3ee708c2f94eb7abe6e0a3b1f2aaf8.png

be58d942fb7a484e9cb1c6e256ddec32.png

a296216657ba4e0dbb854c9ff545815c.png

30db83edcf234ed185f7c923eafbd339.png

7d4e4d3df5af4d0385b1a19dc2491beb.png

44aaa6652cc14f73abe7826250f10751.png

5700846bd782452d99844dae134da757.png

faadf2f4ac494f62a92207135b6b8c9f.png

ce1b8884fa1a4005816d242627a7b64f.png

800e6cdc836741cfb697ec7bfdbc5cb4.png

763d2830189f401e9ee466bbe7a68d63.png69e940f440f245e9a0cec55a0b7f7888.png

        到这里为止,把返回的对象进行序列化,只是将其转成字符并缓存起来,回到ObjectWriter#writeValue()方法,代码如下:

350e52530add4abc8a87f900faec8db3.png

8968041089af49fa8f0a29329e48c69b.png

2a72ff28175243409bff75d10097e30d.png

        到这里为止,SpringMVC的返回值处理讲完了,SpringMVC剩下的内容将在下一篇博文中讲,敬请期待~

 

 

 

 

 

 

 

 

 

 

 

 

 

 

相关文章:

SpringMVC源码深度解析(中)

接上一遍博客《SpringMVC源码深度解析(上)》继续聊。最后聊到了SpringMVC的九大组建的初始化,以 HandlerMapping为例,SpringMVC提供了三个实现了,分别是:BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、RouterFunctio…...

Mojo模型动态批处理:智能预测的终极武器

标题:Mojo模型动态批处理:智能预测的终极武器 在机器学习领域,模型的灵活性和可扩展性是至关重要的。Mojo模型(Model-as-a-Service)提供了一种将机器学习模型部署为服务的方式,允许开发者和数据科学家轻松…...

人、智能、机器人……

在遥远的未来之城,智能时代如同晨曦般照亮了每一个角落,万物互联,机器智能与人类智慧交织成一幅前所未有的图景。这座城市,既是科技的盛宴,也是人性与情感深刻反思的舞台。 寓言:《智光与心影》 在智能之…...

SpringCloud------Sentinel(微服务保护)

目录 雪崩问题 处理方式!!!技术选型 Sentinel 启动命令使用步骤引入依赖配置控制台地址 访问微服务触发监控 限流规则------故障预防流控模式流控效果 FeignClient整合Sentinel线程隔离-------故障处理线程池隔离和信号量隔离​编辑 两种方式优缺点设置方式 熔断降级-----…...

【无标题】Elasticsearch for windows

一、windows安装Elasticsearch 1、Elasticsearch:用于存储数据、计算和搜索; 2、Logstash/Beats:用于数据搜集 3、Kibana:用于数据可视化 以上三个被称为ELK,常用语日志搜集、系统监控和状态分析 Elasticsearch安…...

Yolo-World网络模型结构及原理分析(一)——YOLO检测器

文章目录 概要一、整体架构分析二、详细结构分析YOLO检测器1. Backbone2. Head3.各模块的过程和作用Conv卷积模块C2F模块BottleNeck模块SPPF模块Upsampling模块Concat模块 概要 尽管YOLO(You Only Look Once)系列的对象检测器在效率和实用性方面表现出色…...

WEB前端06-BOM对象

BOM浏览器对象模型 浏览器对象模型:将浏览器的各个组成部分封装成对象。是用于描述浏览器中对象与对象之间层次关系的模型,提供了独立于页面内容、并能够与浏览器窗口进行交互的对象结构。 组成部分 Window:浏览器窗口对象 Navigator&…...

Android11 framework 禁止三方应用开机自启动

Android11应用自启动限制 大纲 Android11应用自启动限制分析验证猜想:Android11 AOSP是否自带禁止三方应用监听BOOT_COMPLETED​方案禁止执行非系统应用监听到BOOT_COMPLETED​后的代码逻辑在执行启动时判断其启动的广播接收器一棍子打死方案(慎用&#…...

Java | Leetcode Java题解之第263题丑数

题目&#xff1a; 题解&#xff1a; class Solution {public boolean isUgly(int n) {if (n < 0) {return false;}int[] factors {2, 3, 5};for (int factor : factors) {while (n % factor 0) {n / factor;}}return n 1;} }...

将AWS RDS MySQL实例从存储未加密改为加密的方案

问题描述&#xff1a; 因为AWS RDS官方文档【1】中已经明确说明&#xff0c;MySQL RDS的存储为EBS卷&#xff0c;用KMS进行RDS加密有如下限制&#xff1a; 您只能在创建RDS的时候&#xff0c;选择加密。对于已经创建的RDS实例&#xff0c;您无法将为加密的实例&#xff0c;直…...

nginx的配置:TLSv1 TLSv1.1 被暴露不安全

要在 Nginx 配置中禁用不安全的 SSL 协议&#xff08;如 TLSv1 和 TLSv1.1&#xff09;&#xff0c;并仅启用更安全的协议&#xff08;如 TLSv1.2 和 TLSv1.3&#xff09;&#xff0c;您可以更新您的 Nginx 配置文件。下面是一个示例配置&#xff1a; # 位于 Nginx 配置文件 (…...

揭开黑箱:目标检测中可解释性的重要性与实现

揭开黑箱&#xff1a;目标检测中可解释性的重要性与实现 在深度学习的目标检测任务中&#xff0c;模型的准确性虽然重要&#xff0c;但模型的决策过程是否透明也同样关键。可解释性&#xff08;Explainability&#xff09;是指模型能够为其预测结果提供清晰、可理解的解释。本…...

Mysql高价语句

一.高级语法的查询语句 1.排序语法&#xff08;默认的排序方式就是升序&#xff09;。 升序ASC&#xff1a;select * from test01 order by name; 降序DESC&#xff1a;select * from test01 order by name desc; 多个列排序&#xff1a;以多个列作为排序&#xff0c;只有第一…...

ArcGIS Pro SDK (九)几何 6 包络

ArcGIS Pro SDK &#xff08;九&#xff09;几何 6 包络 文章目录 ArcGIS Pro SDK &#xff08;九&#xff09;几何 6 包络1 构造包络2 构造包络 - 从 JSON 字符串3 合并两个包络4 与两个包络相交5 展开包络6 更新包络的坐标 环境&#xff1a;Visual Studio 2022 .NET6 ArcGI…...

单链表<数据结构 C版>

目录 概念 链表的单个结点 链表的打印操作 新结点的申请 尾部插入 头部插入 尾部删除 头部删除 查找 在指定位置之前插入数据 在任意位置之后插入数据 测试运行一下&#xff1a; 删除pos结点 删除pos之后结点 销毁链表 概念 单链表是一种在物理存储结构上非连续、非顺序…...

监控电脑进程,避免程序在打开前就已经在运行

文章目录 一、文章的目的&#xff08;适用于windows&#xff09;二、处理方式三、进程查看的内容在窗口端的演示四、附上代码例子四、通过os.kill的方式&#xff0c;再回到原来的表格时&#xff0c;会出现如下错误提示&#xff1a; 一、文章的目的&#xff08;适用于windows&am…...

【MySQL进阶篇】存储对象:视图、存储过程及触发器

一、视图 1、介绍 视图&#xff08;view&#xff09;是一种虚拟存在的表。视图中的数据并不在数据库中实际存在&#xff0c;行和列数据来定义视图的查询中使用的表&#xff08;基表&#xff09;&#xff0c;并且是在使用视图时动态生成的。 通俗的讲&#xff0c;视图只保存了…...

算法day05 master公式估算递归时间复杂度 归并排序 小和问题 堆排序

2.认识O(NlogN)的排序_哔哩哔哩_bilibili master公式 有这样一个数组&#xff1a;【0&#xff0c;4&#xff0c;2&#xff0c;3&#xff0c;3&#xff0c;1&#xff0c;2】&#xff1b;假设实现了这样一个sort()排序方法&#xff0c; 将数组二分成左右两等分&#xff0c;使用so…...

基于jeecgboot-vue3的Flowable流程仿钉钉流程设计器-支持VForm3表单的选择与支持

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、初始化的时候加载表单 /** 查询表单列表 */ const getFormList () > {listForm().then(res > formOptions.value res.result.records) } 2、开始节点的修改&#xff0c;增加表…...

【刷题汇总 -- 压缩字符串(一)、chika和蜜柑、 01背包】

C日常刷题积累 今日刷题汇总 - day0181、压缩字符串(一)1.1、题目1.2、思路1.3、程序实现 2、chika和蜜柑2.1、题目2.2、思路2.3、程序实现 3、 01背包3.1、题目3.2、思路3.3、程序实现 -- dp 4、题目链接 今日刷题汇总 - day018 1、压缩字符串(一) 1.1、题目 1.2、思路 读完…...

反激电源设计避坑:空载炸管、RCD吸收烧电阻?聊聊DCM模式下那些容易忽略的细节

反激电源实战陷阱解析&#xff1a;从空载炸管到RCD失效的深度拆解 实验室里弥漫着焦糊味&#xff0c;示波器上那条本该稳定的波形突然飙升——这可能是每个电源工程师都经历过的噩梦时刻。反激拓扑看似简单&#xff0c;但当你的设计从仿真进入实测阶段&#xff0c;各种"幽…...

想转行做产品经理?看看你身上有没有这5个“隐藏技能”

在数字经济飞速发展的当下&#xff0c;产品经理早已不是互联网行业的“专属岗位”&#xff0c;而是横跨互联网、硬件、金融、制造业等多个领域的核心角色——连接用户需求与技术实现&#xff0c;主导产品从创意到落地的全流程&#xff0c;被称为“CEO的学前班”。正因如此&…...

实战避坑!从WMS视角看Android UI线程优化:为什么主线程耗时必掉帧?

从WMS到Choreographer&#xff1a;Android主线程耗时操作导致丢帧的底层原理与实战优化 当你在Android应用中滑动列表时突然出现卡顿&#xff0c;或是界面渲染出现明显延迟&#xff0c;这背后往往隐藏着主线程耗时操作与WMS&#xff08;WindowManagerService&#xff09;、Chor…...

MedGemma X-Ray 场景应用:基层医生的AI辅助阅片实战指南

MedGemma X-Ray 场景应用&#xff1a;基层医生的AI辅助阅片实战指南 1. 基层医疗的痛点与AI解决方案 在基层医疗机构&#xff0c;放射科医生常常面临两大挑战&#xff1a;一是阅片经验相对不足&#xff0c;二是工作负荷过重。一张胸部X光片可能包含数十个需要观察的关键点&am…...

OpenClaw人人养虾:接入Matrix

Matrix 是一个开放的去中心化通讯协议&#xff08;Decentralized Communication Protocol&#xff09;&#xff0c;任何人都可以搭建自己的 Homeserver&#xff08;家服务器&#xff09;并与全球 Matrix 网络互联。OpenClaw 通过 Matrix Client-Server API 实现接入。 前置要求…...

LEDPatternLib:非阻塞LED动画库设计与嵌入式实践

1. 项目概述LEDPatternLib 是一款面向嵌入式 LED 动画控制的轻量级、模块化 Arduino 库&#xff0c;专为资源受限的微控制器平台设计。其核心目标并非替代底层驱动&#xff0c;而是构建在成熟硬件抽象层之上的非阻塞&#xff08;non-blocking&#xff09;模式动画调度框架。该库…...

MecanumBase:轻量级全向轮运动学逆解C库

1. MecanumBase 库概述MecanumBase 是一个专为全向移动机器人设计的轻量级底层控制库&#xff0c;核心目标是将复杂的轮式运动学解耦为工程师可直观理解的输入指令&#xff1a;平移方向角&#xff08;θ&#xff09;与旋转角速度&#xff08;ω&#xff09;。该库不依赖任何特定…...

Lattice FPGA开发实战:Diamond与ModelSim协同仿真环境搭建全攻略

1. 环境准备&#xff1a;软件安装与基础配置 第一次接触Lattice FPGA开发时&#xff0c;最头疼的就是仿真环境的搭建。我清楚地记得去年接手MachXO2项目时&#xff0c;光是让Diamond和ModelSim这两个"老伙计"协同工作就折腾了整整两天。不过别担心&#xff0c;跟着我…...

经典概率题:飞机座位分配问题(LeetCode 1227)超详细解析

一、题目背景与描述这是一道非常经典的概率与逻辑推理面试题&#xff0c;也是 LeetCode 第 1227 题「飞机座位分配概率」。题目描述有 n 位乘客即将登机&#xff0c;飞机正好有 n 个座位。第一位乘客的票丢了&#xff0c;他随机选一个座位坐下。剩下的乘客&#xff1a;如果自己…...

【单片机】内核中断及NVICPending

红色框住的是M3内核中断&#xff0c;青色框住的默认打开&#xff0c;不可关闭中断&#xff08;除NMI外可屏蔽&#xff09;。包括SysTick在内无需NVIC_EnableIRQ&#xff0c;也无需在中断处理函数里清标志位。NVIC_SetPendingIRQ和NVIC_ClearPendingIRQ基本用不到&#xff0c;任…...