SpringMVC源码阅读:定位Controller
1.前言
SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧
本文将通过源码分析,弄清楚SpringMVC如何找到我们定义的Controller
2.源码分析
org.springframework.web包有49017行代码,我们不可能去逐行阅读,那么,如何寻找源码的踪迹?

顺着上篇博文的思路回到DispatcherServlet类的doDispatch方法,我们获取到了HandlerExecutionChain

点开getHandler方法,发现HandlerExecutionChain是通过HandlerMapping获取的。
DispatcherServlet分析参见SpringMVC源码阅读:核心分发器DispatcherServlet
在943行,我们在这里获取HandlerAdapter,获取到各种argumentResolvers,用来解析参数,还能获取到各种returnValueHandlers

对HandlerMapping ctrl+h查看类继承关系,找到RequestMappingHandlerMapping

我们找到了核心类RequestMappingHandlerMapping,注释说,该类用于创建@RequestMapping和@Controller注解的实例,在3.1版本加入,这正是我们所需要的

根据官方文档提示,我们在调用RequestMappingHandlerMapping的时候真正调用的是HandlerMethod,打开HandlerMethod(注意是org.springframework.web包下)
HandlerMethod是3.1版本引入的,为参数、返回值和注解提供便捷的封装
打开HandlerMethod的子类InvocableHandlerMethod,发现有WebDataBinderFactory,HandlerMethodArgumentResolverComposite,ParameterNameDiscoverer,这三个属性显然是用来处理请求参数的
再打开InvocableHandlerMethod的子类ServletInvocableHandlerMethod,发现有HandlerMethodReturnValueHandlerComposite,这个属性显然是用来处理响应、返回值的
打开HandlerAdpter的子类RequestMappingHandlerAdapter,我们看看它到底做了什么

实例化ServletInvocableHandlerMethod,注入HandlerMethodArgumentResolverComposite,HandlerMethodReturnValueHandlerComposite和ParameterNameDiscoverer,在4.2版本以前,ServletInvocableHandlerMethod在createRequestMappingMethod方法中实例(已废弃,该方法已删除)
现在我们看看HandlerMethod子类InvocableHandlerMethod到底做了什么,浏览器输入http://localhost:8080/springmvcdemo/employee/detail/1
@RequestMapping(method = RequestMethod.GET, value = "/detail/{employeeId}",
produces={"application/json; charset=UTF-8"})
@ResponseBody
public ModelAndView detail(@PathVariable Integer employeeId, ModelAndView view) {
view.setViewName("employee/form");
view.addObject("employee", employeeService.getById(employeeId));
view.addObject("depts", deptService.listAll());
return view;
}

147行获取参数详细信息,如参数名称,148行之后解析参数值Value
parameters在MethodParameter初始化完毕后的数据如下

我们有Integer和ModelAndView类型的两个参数,第一个参数指明了名称叫做"employeeId",第二个未指明名称,所以为null。现在,我们打开MethodParameter类,看看这些参数信息是从哪里来的

在MethodParameter类里,我们看到了参数的原始形态,仅有parameterIndex和nestingLevel。parameterIndex=0表示第一个参数,等于2表示第二个参数,以此类推

这里定义了拷贝构造函数,初始化参数的详细信息,获取每个属性的方法园友可以自行打断点查看,不再逐个赘述。
再回到InvocableHandlerMethod类,回到148行,我们看看参数值是如何获取的,先看args是什么

不难理解,第一个参数"employeeId"=1,第二个参数是null,继续往下走

148行声明Object数组,存储参数值,151行初始化ParameterNameDiscovery,仅为之后方法调用可以使用discover,并未真正解析参数
158~159行,调用HandlerMethodArgumentResolverComposite解析并得到参数值
回到RequestMappingHandlerMapping的父类AbstractHandlerMethodMapping,我们看看initHandlerMethods方法做了什么,启动服务会进入该方法

197~199行初始化所有Bean并获取beanName
205行获取Bean类型

213行isHandler判断bean类型是不是RequestMapping或者Controller,在子类RequestMappingHandlerMapping实现
214行寻找HandlerMethod,218行调用所有被侦测到的HandlerMethod
点开detectHandlerMethods方法,看看它具体做了什么

226~228利用反射依次获取Controller,打断点我发现handlerType和userType内容是一致的,getUserClass方法注释说明是为了获取指定handlerType的类,我觉得这一步多此一举
230行依次获取Controller所有方法

235行getMappingForMethod方法由当前类(AbstractHandlerMethodMapping)的子类RequestMappingHandlerMapping的实现,返回RequestMappingInfo
248行遍历methods,取出方法,249行泛型T是RequestMappingInfo
250行注册HandlerMethod

打破砂锅弄到底,继续进入getMappingForMethod方法,ctrl+alt+b快速跳转到子类实现
再进入createRequestMappingInfo方法
205行依次获取Controller的方法上@RequestMapping注解的属性

208行requestMapping不为空则调用createRequestMappingInfo重载方法(两个参数)用来构造RequestMappingInfo
208行点进去createRequestMappingInfo重载方法,一探究竟

继续深入,进入RequestMappingInfo,看这个类在做什么,按ctrl+alt+u,看类的接口实现关系

看来,RequestMappingInfo实现了RequestCondition,在RequestMappingInfo类中我们观察combine方法,它把路径、方法、参数、头等信息进行了combine操作,combine是指将两个或多个实例根据规则进行组合

打开RequestCondition子类PatternsRequestCondition,找到combine方法,在169行ctrl+alt+b跳转至实现类AntPathMatcher combine方法,此方法初始化才会进入

550行以"/*"结尾,截取再拼接,作者注释很清楚,不赘述
556行以"/**"结尾,直接拼接,请看注释

其他的AbstractRequestCondition子类不再介绍,园友可以自己断点调试看看

3.测试
写一个Controller
@Controller
@RequestMapping("wildcard")
public class TestWildcardController { @RequestMapping("/test/**")
@ResponseBody
public String test1(ModelAndView view) {
view.setViewName("/test/test");
view.addObject("attr", "TestWildcardController -> /test/**");
return String.valueOf(view);
} @RequestMapping("/test/*")
@ResponseBody
public String test2(ModelAndView view) {
view.setViewName("/test/test");
view.addObject("attr", "TestWildcardController -> /test*");
return String.valueOf(view);
} @RequestMapping("test?")
@ResponseBody
public String test3(ModelAndView view) {
view.setViewName("/test/test");
view.addObject("attr", "TestWildcardController -> test?");
return String.valueOf(view);
}
}
输入http://localhost:8080/springmvcdemo/wildcard/test/fff111,结果如下,进入@RequestMapping("/test/*")

输入http://localhost:8080/springmvcdemo/wildcard/test/fff111/sss,结果如下,进入@RequestMapping("/test/**")

根据我们刚才源码分析,输入/test/fff111,AntPathMatcher combine方法帮我们"/test/*".substring(0,6),然后和fff111 concat最终成为/test/fff111

输入/test/fff111/sss,就简单了,直接concat,也就是说,test后面可以加上任意个参数
在浏览器输入http://localhost:8080/springmvcdemo/wildcard/test2,进入@RequestMapping("/test?")

test?,只能匹配一个,输入test会直接进入/test/**
在HandlerExecutionChain 67行获取到我们访问的Controller的HandlerMethod,详细过程参照源码分析部分

4.总结
MethodParameter封装方法参数,HandlerMethod实例化的时候初始化MethodParameter数组,再交由合适的HandlerMethodArgumentResolver(HandlerMethodArgumentResolverComposite的父类)处理
RequestCondition处理请求映射关系,使用combine方法合并不同的条件,getMatchingConditon方法获取映射,compareTo方法为映射排序
RequestMappingInfo实现RequestCondition,集中将各种RequestCondition进行combine,getMatchingConditon,compareTo
RequestMappingHandlerMapping处理请求与HandlerMethod映射关系,找出@RequestMapping和@Controller修饰的类和方法
5.参考
https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#mvc-servlet
SpringMVC源码阅读:定位Controller的更多相关文章
- SpringMVC源码阅读:Controller中参数解析
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读系列汇总
1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...
- SpringMVC源码阅读:属性编辑器、数据绑定
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:Json,Xml自动转换
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:过滤器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:拦截器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:核心分发器DispatcherServlet
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将介绍SpringMVC的核 ...
- SpringMVC源码阅读:视图解析器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:异常解析器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
随机推荐
- 面向对象编程思想(前传)--你必须知道的javascript(转载)
原文地址:http://www.cnblogs.com/zhaopei/p/6623460.html阅读目录 什么是鸭子类型 javascript的面向对象 封装 继承 多态 原型 this指向 ...
- memcached分布式缓存系统
在数据驱动的Web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵活.此时 ...
- H2内存数据库支持存储到文件
准备工作 1.下载JDK(本人下载的版本为JDK1.7).设置环境变量JAVA_HOME,设置PATH(%JAVA_HOME%\bin%). 2.下载并解压:h2-2014-07-13.zip 官网下 ...
- 权限管理系统系列之WCF通信
目录 权限管理系统系列之序言 首先说下题外话,有些园友看了前一篇[权限管理系统系列之序言]博客加了QQ群(186841119),看了我写的权限管理系统的相关文档(主要是介绍已经开发的功能),给出了一 ...
- javascript中的with关键字
说起js中的with关键字,很多小伙伴们的第一印象可能就是with关键字的作用在于改变作用域,然后最关键的一点是不推荐使用with关键字.听到不推荐with关键字后,我们很多人都会忽略掉with关键字 ...
- CC2530学习路线-基础实验-GPIO 按键控制LED灯亮灭(2)
目录 1.前期预备知识 1.1 新大陆Zigbee模块按键电路图 1.2 CC2530相关寄存器 1.3 CC2530中断走向图 1.4 使用C语言为51单片机编写中断程序 1.5 *函数指针 2. ...
- 【cocos2d-x 3.0-Mac配置篇】
就在昨天触控正式发布了3.0正式版本... 在这个喜大普奔的日子里,我们又开始了新一轮的革命,先不说其他的,再来看看3.0目录文件里面有什么? 首先是精简了很多,无论是从目录结构,和所用到的工具类,都 ...
- 上云、微服务化和DevOps,少走弯路的办法
本文由 网易云发布. 作者:张亮 如果说一个项目的发展历程就像一段未知的旅程,那<云原生应用架构实践>就像一张地图,基于前人的探索标明了在这段旅途中将会碰到的障碍,并注明了越过这些障碍的 ...
- Python中进程和线程的总体区别
Num01–>线程 线程是操作系统中能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位. 一个线程指的是进程中一个单一顺序的控制流. 一个进程中可以并发多条线程,每条线程并行 ...
- MySQL(增删改查补充)
SQL语句数据行操作补充 create table tb12( id int auto_increment primary key, ...