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
来源: https://www.cnblogs.com/Java-Starter/p/10315964.html