前文

Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。

Springmvc的优点:

  1. 可以支持各种视图技术,而不仅仅局限于JSP;
  2. 与Spring框架集成(如IoC容器、AOP等);
  3. 清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。
  4. 支持各种请求资源的映射策略。

请求映射器源码解析

这些优秀的特性使得它在企业级开发中使用率超过98%,如此优秀的框架,你是否疑惑过,在一个请求到达后,是如何被SpringMvc拦截到并处理的?

相信大家对上面的流程图都很熟悉,或多或少无论是在准备面试的时候,还是自己学习的时候,都会接触到这个流程图,我见过很多的人,对着这个图死记硬背!我也面试过一些技术人员,问到这块知识,仰着头闭着眼(夸张一下)把这块知识说出来,再往深了问一点就懵逼,归根到底就是对框架理解不够深刻。

SpringMVC是如何感知到每个方法对应的url路径的?

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping 实现 org.springframework.beans.factory.InitializingBean 覆盖 afterPropertiesSet方法,这个方法会在Spring容器初始化的时候回调该方法

该方法类定义为

@Override
public void afterPropertiesSet() {initHandlerMethods();
}
复制代码

调用initHandlerMethods方法,那么initHandlerMethods里面干了什么事情呢?对该方法逐步分析!

/*** Scan beans in the ApplicationContext, detect and register handler methods.* @see #getCandidateBeanNames()* @see #processCandidateBean* @see #handlerMethodsInitialized*/
protected void initHandlerMethods() {for (String beanName : getCandidateBeanNames()) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {processCandidateBean(beanName);}}handlerMethodsInitialized(getHandlerMethods());
}

首先 getCandidateBeanNames() 方法,我们看它的定义

/*** Determine the names of candidate beans in the application context.* @since 5.1* @see #setDetectHandlerMethodsInAncestorContexts* @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors*/
protected String[] getCandidateBeanNames() {return (this.detectHandlerMethodsInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :obtainApplicationContext().getBeanNamesForType(Object.class));
}

  • 这个方法本质的目的就是为了从bean容器中,获取所有的bean,为什么是获取全部的 因为它是基于Object.class类型来获取的类,故而是全部的类,但是这个方法其实深究起来,知识点很多,因为它涉及到Spring父子容器的知识点,所以我决定,后面花一篇文档单独去讲,这里你只需要知道,这个方法可以获取Spring容器里面所有的bean然后返回!

initHandlerMethods() 获取到所有的bean之后然后循环遍历,我们将目光聚集在循环体内部的processCandidateBean方法

protected void processCandidateBean(String beanName) {Class<?> beanType = null;try {beanType = obtainApplicationContext().getType(beanName);}catch (Throwable ex) {// An unresolvable bean type, probably from a lazy bean - let's ignore it.if (logger.isTraceEnabled()) {logger.trace("Could not resolve type for bean '" + beanName + "'", ex);}}if (beanType != null && isHandler(beanType)) {detectHandlerMethods(beanName);}
}

  • beanType = obtainApplicationContext().getType(beanName); 这个方法是基于bean名称获取该类的Class对象
  • isHandler(beanType) 这个方法是判断该类是是加注了Controller注解或者RequestMapping
@Override
protected boolean isHandler(Class<?> beanType) {return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

  • detectHandlerMethods(Object handler)
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {return getMappingForMethod(method, userType);}catch (Throwable ex) {throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);}});

内部该段逻辑可以遍历某个类下所有的方法

  • getMappingForMethod(method, userType); 这个方法的内部做了什么呢? 该i方内部读取所有的映射方法的所有定义,具体的逻辑如下
设置了该方法 的映射路径,方法对象,方法参数,设置的方法请求头,消费类型,可接受类型,映射名称等信息封装成RequestMappingInfo对象返回!

  • getPathPrefix(handlerType); 该方法是处理方法前缀,如果存在和前者方法级别的合并
  • 最终返回一个方法与方法描述信息的map映射集合(Map<Method, RequestMappingInfo>),循环遍历该集合! Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);找到该方法的代理方法! registerHandlerMethod(handler, invocableMethod, mapping);注册该方法! 我们深入该方法摒弃其他与本文无关的代码,会发现这么一段代码!

会发现,我们方法上标注的 url会和前面读取的该方法的定义绑定在一个叫做 urlLookup的方法里面,请大家记住这个方法,这个方法对我们理解SpringMvc的处理逻辑有大用处!

3.请求获取逻辑源码解析

现在,整个工程所有对应的@requestMapping的方法已经被缓存,以该方法为例子!

@RestController
public class TestController {@RequestMapping("test")public String test(){return "success";}
}

现在在urlLookup属性里面就有一个 key为test,value为test()方法详细定义的 k:v键值对:v:

我们看下下面这个类图,DispatcherServlet这个关键的中央类,实际上是Servlet的子类,熟悉Servlet的同学都知道,之前在做Servlet开发的时候,所有的请求经过配置后都会被内部的doget和dopost方法拦截,至此SpringMvc为什么能够拦截URL也就不难分析了,拦截到url后,进入如下的流程调用链!

请求经由 org.springframework.web.servlet.FrameworkServlet#doGet捕获,委托给org.springframework.web.servlet.FrameworkServlet#processRequest方法,最后在调用org.springframework.web.servlet.DispatcherServlet#doService来处理真正的逻辑!

我们看一下这个方法里面的一些主要逻辑吧!

org.springframework.web.servlet.DispatcherServlet#doDispatch调用org.springframework.web.servlet.DispatcherServlet#getHandler方法,再次调用org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler经由org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal方法的org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod的org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#getMappingsByUrl

讲过这么长的调用链是不是懵了,此时我们终于看到了正主!

/*** Return matches for the given URL path. Not thread-safe.* @see #acquireReadLock()*/
@Nullable
public List<T> getMappingsByUrl(String urlPath) {return this.urlLookup.get(urlPath);
}

这段代码是不是熟悉?这就是我们Spring容器在初始化的时候将url和方法定义放置的那个属性,现在Spring容器经由DispatcherServlet拦截请求后又重新找到该方法,并且返回!此时就完成了MVC流程图里面的HandlerMapping处理映射器的部分!

本章关于请求映射器的源码分析到这也就结束了,后续作者会将处理适配器,处理器,视图解析器一一讲明白,其实后续的逻辑也就很简单了,简单来说,拿到方法后反射执行该方法(不一定,一般场景是这样),然后拿到返回值,判断是否有@responseBody注解,判断是否需要转换成json,再通过write写回到页面!大致流程就是这样,详细过程作者后续会写!

经过今天的流程分析,你能否基于Servlet写一个属于自己的SpringMvc呢?

初始化请求例子_当一个http请求来临时,SpringMVC究竟偷偷帮你做了什么?相关推荐

  1. springmvc是什么_当一个http请求来临时,SpringMVC究竟偷偷帮你做了什么?

    前文 Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成 ...

  2. ajax调用上一个ajax,关于jquery:当频繁使用ajax请求调用函数时,如何在处理下一个请求之前等待上一个ajax请求完成?...

    本问题已经有最佳答案,请猛点这里访问. 我在我的代码中有一个ajax请求,如下所示: function show_detail() { $('#product_'+index).html(' load ...

  3. cefsharp 发送请求服务器_超高性能管线式HTTP请求(实践·原理·实现)

    来源:https://www.cnblogs.com/lulianqi/p/8167526.html 这里的高性能指的就是网卡有多快请求发送就能有多快,基本上一般的服务器在一台客户端的压力下就会出现明 ...

  4. python请求库_如何使用Python请求库发出post请求?

    我在Postman中使用以下过滤器在Web API中发出POST请求,但无法使用请求库在Python中发出简单的POST请求. 首先,我向这个URL(http://10.61.202.98:8081/ ...

  5. wx.chooseimage 超过了最大请求长度_一次 HTTP 请求到底经历了什么?

    作者:木木匠 链接:https://url.cn/5ER9kt2 今天这篇文章我们用抓包分析工具来分析 HTTP 请求是怎么样的? 环境准备 本来是想找个网站进行抓包分析的,但是正式环境的网站 HTT ...

  6. 设置公共请求参数_封装一个useFetch实现页面销毁取消请求

    前端业务经常会出现这样一类问题,当用户网速过慢或是其他特殊情况下,该页面的请求还未完成,用户就已经点击其他页面跳出去了.理想状态下请求也是应该终止掉的,所以我们应该想办法将请求和页面卸载关联在一起. ...

  7. axios创建实例对象发送ajax请求_解决一个网页请求多个服务器场景---axios工作笔记009

    然后我们再去看看,我们利用 axios去创建实例对象来发送ajax请求 可以看到上面我们创建了一个duanzi的axios对象. 然后我们在这个duanzi的axios对象中,指定默认的baseURL ...

  8. http请求头获取请求链接_我们如何设计文件请求链接

    http请求头获取请求链接 File Request Links is a new feature we implemented which allows users to receive files ...

  9. 请求拦截_实战SpringCloud通用请求字段拦截处理

    背景 以SpringCloud构建的微服务系统为例,使用前后端分离的架构,每个系统都会提供一些通用的请求参数,例如移动端的系统版本信息.IMEI信息,Web端的IP信息,浏览器版本信息等,这些参数可能 ...

  10. python request请求参数_使用python将请求的requests headers参数格式化方法

    如下所示: import json # 使用三引号将浏览器复制出来的requests headers参数赋值给一个变量 headers = """ Host: zhan. ...

最新文章

  1. leetCode题解之反转二叉树
  2. 第八周实践项目10 稀疏矩阵的十字链表表示
  3. 电子书下载 | 超实用!阿里售后专家的 K8s 问题排查案例合集
  4. linux android ndk
  5. 魅思V20全新正规视频系统源码
  6. ORA-600_16703比特币攻击案例分析
  7. Ajax异步获取html数据中包含js方法无效的解决方法
  8. 三年研发、数亿美元成本,Mate 20的“大杀器”麒麟980是怎样炼成的?
  9. java异步文件读写文件,Java AsynchronousFileChannel和Future读取文件
  10. 数据分析最具价值的49个案例(建议收藏)
  11. isis软件添加源代码c语言,ProteusISIS和Keil软件入门学习..doc
  12. Java:idea查看JDK源码
  13. ideaIU-2017.3.4安装破解图文教程详细步骤
  14. Maven 解决parent项目下部分子项目的依赖问题
  15. 云计算与虚拟化的关系是什么?
  16. 使用GO实现尚硅谷家庭记账系统
  17. Windows平台快速安装MongoDB和Robo 3T
  18. 对于uniapp的项目,获取设备的一些设备id,首次登陆设备的首台绑定,以及对项目的版本号进行对比进行app升级
  19. 2014华中首届手游创意大赛
  20. 在线IDE- Gitpod介绍

热门文章

  1. 肝毒净-道格拉斯实验室
  2. 解决navicat在未联网的情况下访问不了MySQL数据库的现象
  3. vue父子组件的传值
  4. 2.5.1 命令与参数
  5. JavaScript中值类型与引用类型
  6. cve-2017-0199metasploit复现过程
  7. Stopwatch 计时器类
  8. #winhec# 开发人员刷屏看点 (视频)
  9. Python eval 函数
  10. 【干货】神经网络初始化trick:大神何凯明教你如何训练网络!