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

关于springboot的Rest请求映射处理的源码分析(二)

前面我们知道了他怎么处理表单映射,这里我们来研究一下,他是如何处理具体请求的。也就是说我有那么多/user你是怎么定位到我在哪个cotroller,并且你是怎么定位到我具体是哪个接口。
这里我们就来逐步定位一下这个问题。

一、组件分析

老路子,我们的所有逻辑都是一个个组件串起来的,我们先把组件一个个拿出来。看看他的功能。

组件1、@RequestMapping

我们的请求接口都标注了这个注解,然后生效的,虽然没啥逻辑,但是还是列出来。

组件2、请求处理器RequestMappingHandlerMapping

我们看到他在WebMvcAutoConfiguration中注册了一堆bean,是用来做请求映射处理的,其中有一个名字就是RequestMappingHandlerMapping 一看就是用来处理我们的@RequestMapping注解请求的。其余还有什么WelcomePageHandlerMapping 是用来处理欢迎页的,也就是那个默认的index.html页面的跳转。我们不管这个,就看到他给了RequestMappingHandlerMapping。

@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {// Must be @Primary for MvcUriComponentsBuilder to workreturn super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,resourceUrlProvider);
}@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),this.mvcProperties.getStaticPathPattern());welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());return welcomePageHandlerMapping;
}

貌似都有了,有了映射,有了映射处理器,此外的就是看逻辑了。

二、源码流程

1、找到入口

我们先明确一下,怎么分析mvc这类源码,我们都知道入口类就是DispatcherServlet。而他再怎么花哨也就是个servlet,既然你是servlet,那就肯定主要逻辑实现在那个叫做doGet和doPost的方法里面。
我们就去找这个方法,可能这个里面没有,但是父类里面绝壁有。我们最后在FrameworkServlet这个类里面找到了这两个方法。

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}/*** Delegate POST requests to {@link #processRequest}.* @see #doService*/
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}......还有什么doDelete之类的不说了。都一样

我们看到他其实是都走了processRequest(request, response);这个方法。最后我们一路翻他的主要实现逻辑依次是processRequest->org.springframework.web.servlet.DispatcherServlet#doService->
org.springframework.web.servlet.DispatcherServlet#doDispatch 这就是我们每次定位MVC问题的那个入口类。就是这么分析来的。
于是我们来看这个类的这个方法。

2、主要逻辑

org.springframework.web.servlet.DispatcherServlet#doDispatch
一些没用的代码我就删了,我们这里主要关注请求映射处理。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {        HttpServletRequest processedRequest = request;                                                            // 删除没用的                                try {                                                                                                     ModelAndView mv = null;                                                                               Exception dispatchException = null;                                                                   try {  // 看是不是文件上传请求,我们这里不关注                                                                                               processedRequest = checkMultipart(request);                                                       multipartRequestParsed = (processedRequest != request);                                           // Determine handler for the current request.  // 看着源码注释也知道他是决定你哪个处理器来处理你的请求的,其实就是这里                                                   mappedHandler = getHandler(processedRequest);                                                     // 后续就是走具体逻辑了,我们只关注他怎么找到请求执行的。后面不关注                                 }                                                                                                     }                                                                                                             

所以我们看到主要逻辑落在了mappedHandler = getHandler(processedRequest);这个方法上面,
我们就来看看这个方法。
org.springframework.web.servlet.DispatcherServlet#getHandler

@Nullable                                                                                    
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的集合,然后看哪个能处理你的这个请求,就直接返回了,我们这里需要debug一下,看看这个集合里面都有啥。
我们把断点打到这里。然后调用一个GET请求。
在这里插入图片描述
在这里插入图片描述
我们看到他其实就是我们组件2的集合,其中第一个就是我们的组件2,而且他里面加载了所有的标注了@ReauestMapping的请求路径的集合,他在初始化加载的时候就封装进去了。于是我们知道他在组件2里面就有了我们所有的请求路径的类,接口,以及其他信息,都封装了。
封装结构为map,其中key就是请求路径,比如/user
value就是你这个路径哪个controller,哪个方法之类的。这样就能一下就找到了。
我们来直接进入这个方法。看看他怎么判断的。我们debug进去。
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 这里就是处理逻辑,我们再进去。Object handler = getHandlerInternal(request);// 省略没用的......return executionChain;
}

最后来到这里。org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<>();/**lookupPath:就是我们的这次请求的路径/user而我们一共有四个/user。分别是post get put delete所以这里directPathMatches 会拿回来四个结果。*/List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {// 这里会根据请求类型,返回其中对的上的,比如我们这次请求的是GET,那就返回GET的那个// /user,把这个请求的处理信息放在了matches集合里面addMatchingMappings(directPathMatches, matches, request);}// 如果一个也没有,全部封装进来,这里我们会找到一个。不走这里if (matches.isEmpty()) {// No choice but to go through all mappings...addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}if (!matches.isEmpty()) {// 取出找到的那一个,这样我们就找到了我们的controller和接口Match bestMatch = matches.get(0);// 如果找到了多个,哥们你url写重复了,抛出异常Ambiguous...之类的if (matches.size() > 1) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));matches.sort(comparator);bestMatch = matches.get(0);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 + "}");}}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);}
}

这样我们就找到了我们的controller和接口,后面就是请求执行了,之前我们看过了,其实是反射。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Furion+SqlSugar+Swagger企业级后端工程师 - 学习路线总目录
  • 爬虫入门学习
  • 基于imx6ull平台opencv的图像采集、ffmpeg推流和Windows端拉流(多线程)
  • easyExcel 导入时,校验每个单元格数据
  • 如何快速判断Excel中选区跨页?
  • 笔记:应用Visual Studio Profiler识别和解决内存泄漏问题
  • UE4 BuildCookRun中的Archive的含义
  • 彩色相机拍照,图片时亮时暗
  • 数据结构(一)——顺序表和单向链表(一对一)
  • Magisk/Riru/LSPosed安装
  • SQLite的安装和使用
  • Java 对象拷贝复制,对象属性拷贝复制
  • 使用叶脊架构和EVPN-VXLAN技术增强数据中心性能
  • 基于深度学习的单目标跟踪系统
  • 跨境专线使用的是何种协议?为何网速很快?
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • [ JavaScript ] 数据结构与算法 —— 链表
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • 10个确保微服务与容器安全的最佳实践
  • CentOS7 安装JDK
  • Cookie 在前端中的实践
  • input实现文字超出省略号功能
  • JavaScript 基本功--面试宝典
  • javascript从右向左截取指定位数字符的3种方法
  • js算法-归并排序(merge_sort)
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • node.js
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • SAP云平台里Global Account和Sub Account的关系
  • 大主子表关联的性能优化方法
  • 深度学习入门:10门免费线上课程推荐
  • 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
  • 推荐一个React的管理后台框架
  • 一、python与pycharm的安装
  • 一个SAP顾问在美国的这些年
  • 如何在招聘中考核.NET架构师
  • ​2021半年盘点,不想你错过的重磅新书
  • #13 yum、编译安装与sed命令的使用
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • (7)svelte 教程: Props(属性)
  • (二)windows配置JDK环境
  • (函数)颠倒字符串顺序(C语言)
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • * 论文笔记 【Wide Deep Learning for Recommender Systems】
  • .net core 控制台应用程序读取配置文件app.config
  • .NET Core引入性能分析引导优化
  • .NET框架
  • // an array of int
  • @cacheable 是否缓存成功_Spring Cache缓存注解
  • @Conditional注解详解
  • @EnableWebSecurity 注解的用途及适用场景
  • @ohos.systemParameterEnhance系统参数接口调用:控制设备硬件(执行shell命令方式)
  • [Android]如何调试Native memory crash issue
  • [android学习笔记]学习jni编程