详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]
目录
前言
SpringMVC是目前主流的Web MVC框架之一。
如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html
我们使用浏览器通过地址 http://ip:port/contextPath/path进行访问,SpringMVC是如何得知用户到底是访问哪个Controller中的方法,这期间到底发生了什么。
本文将分析SpringMVC是如何处理请求与Controller之间的映射关系的,让读者知道这个过程中到底发生了什么事情。
源码分析
在分析源码之前,我们先了解一下几个东西。
1.这个过程中重要的接口和类。
HandlerMethod类:
Spring3.1版本之后引入的。 是一个封装了方法参数、方法注解,方法返回值等众多元素的类。

它的子类InvocableHandlerMethod有两个重要的属性WebDataBinderFactory和HandlerMethodArgumentResolverComposite, 很明显是对请求进行处理的。
InvocableHandlerMethod的子类ServletInvocableHandlerMethod有个重要的属性HandlerMethodReturnValueHandlerComposite,很明显是对响应进行处理的。
ServletInvocableHandlerMethod这个类在HandlerAdapter对每个请求处理过程中,都会实例化一个出来(上面提到的属性由HandlerAdapter进行设置),分别对请求和返回进行处理。 (RequestMappingHandlerAdapter源码,实例化ServletInvocableHandlerMethod的时候分别set了上面提到的重要属性)

MethodParameter类:
HandlerMethod类中的parameters属性类型,是一个MethodParameter数组。MethodParameter是一个封装了方法参数具体信息的工具类,包括参数的的索引位置,类型,注解,参数名等信息。
HandlerMethod在实例化的时候,构造函数中会初始化这个数组,这时只初始化了部分数据,在HandlerAdapter对请求处理过程中会完善其他属性,之后交予合适的HandlerMethodArgumentResolver接口处理。
以类DeptController为例:
@Controller
@RequestMapping(value = "/dept")
public class DeptController {
@Autowired
private IDeptService deptService;
@RequestMapping("/update")
@ResponseBody
public String update(Dept dept) {
deptService.saveOrUpdate(dept);
return "success";
}
}
(刚初始化时的数据)

(HandlerAdapter处理后的数据)

RequestCondition接口:
Spring3.1版本之后引入的。 是SpringMVC的映射基础中的请求条件,可以进行combine, compareTo,getMatchingCondition操作。这个接口是映射匹配的关键接口,其中getMatchingCondition方法关乎是否能找到合适的映射。

RequestMappingInfo类:
Spring3.1版本之后引入的。 是一个封装了各种请求映射条件并实现了RequestCondition接口的类。
有各种RequestCondition实现类属性,patternsCondition,methodsCondition,paramsCondition,headersCondition,consumesCondition以及producesCondition,这个请求条件看属性名也了解,分别代表http请求的路径模式、方法、参数、头部等信息。

RequestMappingHandlerMapping类:
处理请求与HandlerMethod映射关系的一个类。
2.Web服务器启动的时候,SpringMVC到底做了什么。
先看AbstractHandlerMethodMapping的initHandlerMethods方法中。



我们进入createRequestMappingInfo方法看下是如何构造RequestMappingInfo对象的。

PatternsRequestCondition构造函数:

类对应的RequestMappingInfo存在的话,跟方法对应的RequestMappingInfo进行combine操作。

然后使用符合条件的method来注册各种HandlerMethod。

下面我们来看下各种RequestCondition接口的实现类的combine操作。
PatternsRequestCondition:


RequestMethodsRequestCondition:
方法的请求条件,用个set直接add即可。

其他相关的RequestConditon实现类读者可自行查看源码。
最终,RequestMappingHandlerMapping中两个比较重要的属性
private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();
private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();
T为RequestMappingInfo。
构造完成。
我们知道,SpringMVC的分发器DispatcherServlet会根据浏览器的请求地址获得HandlerExecutionChain。
这个过程我们看是如何实现的。
首先看HandlerMethod的获得(直接看关键代码了):

这里的比较器是使用RequestMappingInfo的compareTo方法(RequestCondition接口定义的)。

然后构造HandlerExecutionChain加上拦截器

实例
写了这么多,来点例子让我们验证一下吧。
@Controller
@RequestMapping(value = "/wildcard")
public class TestWildcardController {
@RequestMapping("/test/**")
@ResponseBody
public String test1(ModelAndView view) {
view.setViewName("/test/test");
view.addObject("attr", "TestWildcardController -> /test/**");
return view;
}
@RequestMapping("/test/*")
@ResponseBody
public String test2(ModelAndView view) {
view.setViewName("/test/test");
view.addObject("attr", "TestWildcardController -> /test*");
return view;
}
@RequestMapping("test?")
@ResponseBody
public String test3(ModelAndView view) {
view.setViewName("/test/test");
view.addObject("attr", "TestWildcardController -> test?");
return view;
}
@RequestMapping("test/*")
@ResponseBody
public String test4(ModelAndView view) {
view.setViewName("/test/test");
view.addObject("attr", "TestWildcardController -> test/*");
return view;
}
}
由于这里的每个pattern都带了*因此,都不会加入到urlMap中,但是handlerMethods还是有的。

当我们访问:http://localhost:8888/SpringMVCDemo/wildcard/test1的时候。
会先根据 "/wildcard/test1" 找urlMap对应的RequestMappingInfo集合,找不到的话取handlerMethods集合中所有的key集合(也就是RequestMappingInfo集合)。
然后进行匹配,匹配根据RequestCondition的getMatchingCondition方法。

最终匹配到2个RequestMappingInfo:

然后会使用比较器进行排序。
之前也分析过,比较器是有优先级的。
我们看到,RequestMappingInfo除了pattern,其他属性都是一样的。
我们看下PatternsRequestCondition比较的逻辑:


因此,/test*的通配符比/test?的多,因此,最终选择了/test?

直接比较优先于通配符。

@Controller
@RequestMapping(value = "/priority")
public class TestPriorityController {
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public String test1(ModelAndView view) {
view.setViewName("/test/test");
view.addObject("attr", "其他condition相同,带有method属性的优先级高");
return view;
}
@RequestMapping()
@ResponseBody
public String test2(ModelAndView view) {
view.setViewName("/test/test");
view.addObject("attr", "其他condition相同,不带method属性的优先级高");
return view;
}
}
这里例子,其他requestCondition都一样,只有RequestMethodCondition不一样。

看出,方法多的优先级越多。

至于其他的RequestCondition,大家自行查看源码吧。
资源文件映射
以上分析均是基于Controller方法的映射(RequestMappingHandlerMapping)。
SpringMVC中还有静态文件的映射,SimpleUrlHandlerMapping。
DispatcherServlet找对应的HandlerExecutionChain的时候会遍历属性handlerMappings,这个一个实现了HandlerMapping接口的集合。
由于我们在*-dispatcher.xml中加入了以下配置:
<mvc:resources location="/static/" mapping="/static/**"/>
Spring解析配置文件会使用ResourcesBeanDefinitionParser进行解析的时候,会实例化出SimpleUrlHandlerMapping。


其中注册的HandlerMethod为ResourceHttpRequestHandler。
访问地址:http://localhost:8888/SpringMVCDemo/static/js/jquery-1.11.0.js


地址匹配到/static/**。
最终SimpleUrlHandlerMapping找到对应的Handler -> ResourceHttpRequestHandler。
ResourceHttpRequestHandler进行handleRequest的时候,直接输出资源文件的文本内容。
总结
大致上整理了一下SpringMVC对请求的处理,包括其中比较关键的类和接口,希望对读者有帮助。
让自己对SpringMVC有了更深入的认识,也为之后分析数据绑定,拦截器、HandlerAdapter等打下基础。
参考资料
http://jinnianshilongnian.iteye.com/blog/1684403
http://my.oschina.net/HeliosFly/blog/212329
详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]的更多相关文章
- 详解SpringMVC请求的时候是如何找到正确的Controller
详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析] 目录 前言 源码分析 重要接口介绍 SpringMVC初始化的时候做了什么 HandlerExecutionCha ...
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转
SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析]
目录 前言 现象 源码分析 实例讲解 关于配置 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.c ...
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析](使用JAXB转换XML)
前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...
- SpringMVC异常处理机制详解[附带源码分析]
目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...
- SpringMVC核心分发器DispatcherServlet分析[附带源码分析]
目录 前言 DispatcherServlet初始化过程 DispatcherServlet处理请求过程 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不 ...
- [转]SpringMVC拦截器详解[附带源码分析]
目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:ht ...
- SpringMVC拦截器详解[附带源码分析]
目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:h ...
- SpringMVC视图机制详解[附带源码分析]
目录 前言 重要接口和类介绍 源码分析 编码自定义的ViewResolver 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门bl ...
随机推荐
- 【PHP自定义显示系统级别的致命错误和用户级别的错误】
使用方法set_error_handler可以自定义用户级别的错误和系统级别的错误信息显示和处理 用户级别的错误使用trigger_error方法产生一个用户级别的错误信息 代码示例: 系统级别的错误 ...
- 在PYTHON3中,使用Asyncio来管理Event loop
#!/usr/bin/env python # -*- coding: utf-8 -*- import asyncio import datetime import time def functio ...
- rhel7初体验
Redhat7界面明显比之前的版本华丽了不少,貌似Redhat对普通用户的使用也要进行普及 可以在安装的同时修改root密码和创建新用户
- 记录一次冷备恢复遇到的 ORA-00304问题
希望通过冷备一个数据库,然后在另外一台数据库进行恢复 1.打tar包 tar -czvf Prod.tar.gz *control01.ctlcontrol02.ctlredo01.logredo02 ...
- CXF学习(4) 处理无法自动转换的复合数据类型
只贴出服务端代码 1.Service接口类 package com.test.hello; import java.util.Map; import javax.jws.WebService; imp ...
- 2016"百度之星" - 初赛(Astar Round2A)All X(数学 矩阵)
All X Accepts: 1281 Submissions: 7580 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536 ...
- 工厂模式/factory模式/创建型模式
工厂模式 普通工厂模式 原本需要new出来的对象,通过一个类的方法去搞定,Factory.build(parameter),类似这种. public interface Sender { public ...
- 安卓app设计规范整理和Android APP设计篇(转)
随着安卓智能手机不停的更新换代.安卓手机系统越来越完美,屏幕尺寸也越来越大啦!比如最近小米的miui 6的发布和魅族手机系统的更新等等. 以小米MIUI6的安卓手机来说,MIUI6进行了全新设计,坚持 ...
- C语言文件操作fclose在NDK引起的BUG
今天在NDK中写了一个简单的写入文件操作: FILE *fp = fopen("/sdcard/test.txt","w"); if(fp == NULL) { ...
- cocos2dx游戏开发——微信打飞机学习笔记(一)——开发准备
一.环境的搭建 1.Windows开发准备: (1)软件下载及安装 •下载Cocos2d-x 最新版本:http://www.cocos2d-x.org/download 或者从Cocos2d-x G ...