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

带着问题读源码——Spring MVC是怎么找到接口实现类的?

引言

我们的产品主打金融服务领域,以B端客户为我们的核心合作伙伴,然而,我们的服务最终将惠及C端消费者。在技术实现上,我们采用了公司自主研发的微服务框架,该框架基于SpringBoot,旨在提供高效、可靠的服务支持。

本文继《生产问题排查系列——未知404状态接口请求》之后,深入探讨并扩展了对我们公司自主研发框架的理解。在上一篇文章中,我们通过应用性能管理工具定位并解决了持续出现的404请求问题。

代码实例

下面给出一个简单的示例,方面读者在后续源码阅读中能着我们的排查思路一起来看上述问题。

pom.xml中引入Spring Boot以及Spring MVC

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.14</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.example</groupId><artifactId>springmvc</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

TestController:定义一个请求接口

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
public class TestController {@RequestMapping("/test")@ResponseBodypublic String test(){return "ok";}
}

源码阅读

下面我们将从一个Spring提供的Health监控请求,一步一步分析Spring MVC是如何转发请求的。

Spring MVC所有的接口请求都将由doDispatch方法负责转发。源码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 确定当前请求处理的实际处理类mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 确定当前请求的处理程序适配器。HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 如果处理程序支持,则处理上次修改的标头。String method = request.getMethod();boolean isGet = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 调用处理程序mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// 从4.3开始,我们也在处理处理程序方法抛出的错误,//使它们可用于@ExceptionHandler方法和其他场景。dispatchException = new NestedServletException("Handler dispatch failed", err);}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// 而不是postHandle和afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// 清理由多部分请求使用的所有资源。if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

监控接口请求会转发给AbstractHandlerMethodAdapter.handle方法处理,源码如下:

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);}

然后请求会转发到AbstractHandlerMethodAdapter.handleInternal方法中,源码如下:

protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;checkRequest(request);// 如果需要,在同步块中执行invokeHandlerMethod。if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) {mav = invokeHandlerMethod(request, response, handlerMethod);}}else {// 没有可用的HttpSession->不需要互斥mav = invokeHandlerMethod(request, response, handlerMethod);}}else {// 根本不需要对会话进行同步。。。mav = invokeHandlerMethod(request, response, handlerMethod);}if (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav;}

然后接口请求会转发到RequestMappingHandlerAdapter.invokeHandlerMethod,进行一系列参数填充后调用invokeAndHandle方法,源码如下:

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}}

此时请求会转发到ServletInvocableHandlerMethod.invokeAndHandle,之后调用InvocableHandlerMethod.invokeForRequest执行方法,源码如下:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}}else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;}}

InvocableHandlerMethod.invokeForRequest会调用InvocableHandlerMethod.doInvoke执行实际方法

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}return doInvoke(args);}

InvocableHandlerMethod.doInvoke会调用实际的bean执行方法。从这一步的getBean()方法我们就能知道执行spring bean是什么类。

protected Object doInvoke(Object... args) throws Exception {Method method = getBridgedMethod();try {if (KotlinDetector.isSuspendingFunction(method)) {return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);}return method.invoke(getBean(), args);}catch (IllegalArgumentException ex) {assertTargetBean(method, getBean(), args);String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");throw new IllegalStateException(formatInvokeError(text, args), ex);}catch (InvocationTargetException ex) {// Unwrap for HandlerExceptionResolvers ...Throwable targetException = ex.getTargetException();if (targetException instanceof RuntimeException) {throw (RuntimeException) targetException;}else if (targetException instanceof Error) {throw (Error) targetException;}else if (targetException instanceof Exception) {throw (Exception) targetException;}else {throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);}}}

总结

上述源码调用流程如下:
在这里插入图片描述

在深入研究Spring MVC的源码调用链路后,有了以下体会:

代码之间的调用关系错综复杂,构成了一个深广交织的网络。通过阅读源码,我们可以发现,从请求的接收到处理再到响应的返回,涉及了众多组件和层次的交互。这种深度的耦合使得没有全景图的情况下,仅凭对各个类功能的碎片化认识,难以形成对框架整体工作机制的实际理解。

为了真正掌握Spring MVC的内在逻辑,后续工作需要着手细致地梳理这些类之间的组织结构,明确它们如何协同工作以及各自的职责边界。这对于后续的框架使用、问题排查乃至自定义扩展都至关重要。

相关文章:

带着问题读源码——Spring MVC是怎么找到接口实现类的?

引言 我们的产品主打金融服务领域&#xff0c;以B端客户为我们的核心合作伙伴&#xff0c;然而&#xff0c;我们的服务最终将惠及C端消费者。在技术实现上&#xff0c;我们采用了公司自主研发的微服务框架&#xff0c;该框架基于SpringBoot&#xff0c;旨在提供高效、可靠的服…...

[NAND Flash 7.1] 闪存系统性能优化方向集锦?AC timing? Cache? 多路并发?

依公知及经验整理,原创保护,禁止转载。 专栏 《深入理解NAND Flash》 <<<< 返回总目录 <<<< ​ 传送门 >>> 总目录 主页: 元存储的博客_CSDN博客 依公开知识及经验整理,如有误请留言。 个人辛苦整理,付费内容,禁止转载。 内容摘要 优…...

【数据结构】分治策略

现场保护和现场恢复 文章目录 分治策略分治法解决问题有以下四个特征&#xff1a;分治法步骤: 递归&#xff1a;解决以下问题&#xff1a;倒序输出整数求最大公约数&#xff08;递归和非递归&#xff09;菲波那切数列 不要尝试间接 要使用直接递归&#xff08;自己调用自己&am…...

【PLC一体机】PLC一体机中如何实现触摸屏和PC电脑的通讯

博主今天准备把之前买的PLC一体机拿出来玩一下&#xff0c;翻看以前的博文&#xff0c;发现没有记录分享PLC一体机中如何实现触摸屏程序下载的内容。 如之前博文介绍的那样&#xff0c;PLC一体机由PLC和触摸屏两部分集成的设备&#xff0c;因此设备内部已经做好了PLC和触摸屏之…...

如何保证订单异步回调的幂等性

保证订单异步回调的幂等性是非常重要的&#xff0c;因为异步通知可能会由于网络问题、支付系统重试或其他原因导致多次发送同一个支付结果通知。以下是一些保证订单异步回调幂等性的常用方法&#xff1a; 接口设计幂等性&#xff1a; 在设计异步通知的接口时&#xff0c;考虑让…...

Linux下vim命令详解

vim #创建或编辑新的文件 #这将在当前目录下创建一个名为fi.txt的新文本文件。如果文件已经存在&#xff0c;将会编辑现有文件。 [rootsever ~]#vim fi.txt #对于普通的文本编辑操作&#xff0c;可以使用以下键盘命令&#xff1a; - i&#xff1a;进入插入模式&#xff…...

机器学习6-逻辑回归

逻辑回归是机器学习中一种常用于二分类问题的监督学习算法。虽然名字中包含“回归”,但实际上它用于分类任务,特别是对于输出为两个类别的情况。逻辑回归通过使用 logistic 函数将输入映射到一个在0,1范围内的概率值,然后根据这个概率值进行分类。 以下是逻辑回归的基本概念…...

关于Clone

关于Clone 一般情况下&#xff0c;如果使用clone()方法&#xff0c;则需满足以下条件。 1、对任何对象o&#xff0c;都有o.clone() ! o。换言之&#xff0c;克隆对象与原型对象不是同一个对象。 2、对任何对象o&#xff0c;都有o.clone().getClass() o.getClass()。换言之&a…...

【C++入门学习指南】:函数重载提升代码清晰度与灵活性

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; C入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、函数重载1.1 函数重载的概念1.2 函数重载的作用1.3 C支持函数重载的原理1.4 扩展 &…...

MySql主从同步,同步SQL_ERROR 1032解决办法

1.登录从库 mysql -u root -p 2.输入命令查看状态 SHOW SLAVE STATUS\G; 3.找到对应的错误数据位置 Slave_IO_Running: YesSlave_SQL_Running: NoReplicate_Do_DB: app_push_centerReplicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Tabl…...

Webpack的性能优化

减少构建时间&#xff1a;使用webpack的缓存功能&#xff0c;通过配置cache: true来利用缓存&#xff0c;减少重复构建时间。 使用多线程或并行构建&#xff0c;可以利用webpack的parallel-webpack或HappyPack插件来实现。 充分利用硬件资源&#xff0c;例如利用多核CPU或者SSD…...

PyTorch中tensor.backward()函数的详细介绍

backward() 函数是PyTorch框架中自动求梯度功能的一部分&#xff0c;它负责执行反向传播算法以计算模型参数的梯度。由于PyTorch的源代码相当复杂且深度嵌入在C底层实现中&#xff0c;这里将提供一个高层次的概念性解释&#xff0c;并说明其使用方式而非详细的源代码实现。 在P…...

Linux 驱动开发基础知识——内核对设备树的处理与使用(十)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…...

编程笔记 html5cssjs 077 Javascript 关键字

编程笔记 html5&css&js 077 Javascript 关键字 一、关键字二、Javascript关键字注意 在计算机编程语言中&#xff0c;关键字&#xff08;Keyword&#xff09;是指那些被编程语言赋予特殊含义、具有预定义用途的保留字。这些词汇不能用作变量名、函数名或其他标识符&…...

LeetCode_19_中等_删除链表的倒数第N个结点

文章目录 1. 题目2. 思路及代码实现&#xff08;Python&#xff09;2.1 计算链表长度2.2 栈 1. 题目 给你一个链表&#xff0c;删除链表的倒数第 n n n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a; h e a d [ 1 , 2 , 3 , 4 , 5 ] , n…...

C++泛编程(3)

类模板基础 1.类模板的基本概念2.类模板的分文件编写3.类模板的嵌套 &#xff08;未完待续...&#xff09; 在往节内容中&#xff0c;我们详细介绍了函数模板&#xff0c;这节开始我们就来聊一聊类模板。C中&#xff0c;类的细节远比函数多&#xff0c;所以这个专题也会更复杂。…...

python基于django的公交线路查询系统mf383

1.个人信息的管理&#xff1a;对用户名&#xff0c;密码的增加、删除等 2.线路信息的管理&#xff1a;对线路的增加、修改、删除等 3.站点信息的管理&#xff1a;对站点的增加、修改、删除等 4.车次信息的管理&#xff1a;对车次的增加、修改、删除等 5.线路查询、站点查询 …...

ElementUI 组件:Container 布局容器实例

ElementUI安装与使用指南 Container 布局容器 点击下载learnelementuispringboot项目源码 效果图 el-container-example.vue&#xff08;Container 布局容器实例&#xff09;页面效果图 项目里el-container-example.vue代码 <script> export default {name: el_cont…...

【数据结构 09】哈希

哈希算法&#xff1a;哈希也叫散列、映射&#xff0c;将任意长度的输入通过散列运算转化为固定长度的输出&#xff0c;该输出就是哈希值&#xff08;散列值&#xff09;。 哈希映射是一种压缩映射&#xff0c;通常情况下&#xff0c;散列值的空间远小于输入值的空间。 哈希运…...

理解和管理Linux文件权限

理解和管理Linux文件权限 文件权限的基本概念和表示方式 文件权限管理在Linux系统中是非常重要的&#xff0c;它决定了谁可以访问、读取、写入或执行文件。文件权限以及所有者、所属组等属性可以通过 ls -l 命令查看。 在 ls -l 命令的输出中&#xff0c;文件的权限通常表示…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

QT3D学习笔记——圆台、圆锥

类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体&#xff08;对象或容器&#xff09;QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质&#xff08;定义颜色、反光等&#xff09;QFirstPersonC…...