Spring mvc源码url路由-我们到底能走多远系列(38)
我们到底能走多远系列38
扯淡:
马航的事,挺震惊的。还是多多珍惜身边的人吧。
主题:
Spring mvc 作为表现层的框架,整个流程是比较好理解的,毕竟我们做web开发的,最早也经常接触的就是一个request进一个response出的http请求。
- URL到框架的映射。
- http请求参数绑定
- http响应的生成和输
下面是网上对spring mvc的整个流程的执行顺序图:
这次慢慢看源码,所以先来看看第一步,就是根据url找到指定的controller来执行的过程。其实就是一个url路由功能实现。
原理可以理解成有一个map存着key是url,值是controller的map,然后来一个url,找到对应的controller就好了。
public interface HandlerMapping {
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping" ; String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern"; String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping" ; String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables"; String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables"; String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes"; /**
* Return a handler and any interceptors for this request. The choice may be made
* on request URL, session state, or any factor the implementing class chooses.
* <p>The returned HandlerExecutionChain contains a handler Object, rather than
* even a tag interface, so that handlers are not constrained in any way.
* For example, a HandlerAdapter could be written to allow another framework's
* handler objects to be used.
* <p>Returns <code> null</code> if no match was found. This is not an error.
* The DispatcherServlet will query all registered HandlerMapping beans to find
* a match, and only decide there is an error if none can find a handler.
* @param request current HTTP request
* @return a HandlerExecutionChain instance containing handler object and
* any interceptors, or <code>null</code> if no mapping found
* @throws Exception if there is an internal error
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; }
HandlerMapping实现其实维护了一个HashMap<String, Object>,其中key是http请求的path信息,value可以是一个字符串,或者是一个处理请求的HandlerExecutionChain
在AbstractUrlHandlerMapping源码:
private final Map <String, Object> handlerMap = new LinkedHashMap< String, Object >();
DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列 HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping。
如果当前的HandlerMapping能够返 回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的 HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可 用的Handler为止。
<bean
class= "org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
<property name= "order" value= "1" />
</bean > <bean id= "urlMapping"
class= "org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" >
<property name= "mappings">
<value>
dealer/login.html=DealerController
customer/login.html=CustomerController
</value>
</property>
<property name= "order" value= "0" />
</bean >
ControllerClassNameHandlerMapping的order为 1 SimpleUrlHandlerMapping的order为0 如果各自配置了相同url对用的Controller那么就优先使用SimpleUrlHandlerMapping的。其实这里应该理解为优先使用SimpleUrlHandlerMapping中的HandlerExecutionChain更为准确,关于HandlerExecutionChain后面详细理解。
/** List of HandlerMappings used by this servlet */
private List<HandlerMapping> handlerMappings; protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger .isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null ;
}
遍历一遍所有的HandlerMapping,每次检查有没有需要的handler,第一个找到的就返回。如此看来这个List<HandlerMapping>应该是有优先级的,饿哦们可以看HandlerMapping的第一个实现类(AbstractHandlerMapping)它就继承了Ordered来获得一个优先级的功能。
那么初始化这个List<HandlerMapping>的源码:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null; if (this .detectAllHandlerMappings ) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils. beansOfTypeIncludingAncestors(context, HandlerMapping.class , true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
// 也就这个方法啦
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME , HandlerMapping.class );
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
} // Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this .handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger .isDebugEnabled()) {
logger.debug( "No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
直接看OrderComparator. sort( this. handlerMappings);的实现:
// 继承Comparator接口
public class OrderComparator implements Comparator<Object> { /**
* Shared default instance of OrderComparator.
*/
public static final OrderComparator INSTANCE = new OrderComparator(); // 实现compare方法
public int compare(Object o1, Object o2) {
boolean p1 = (o1 instanceof PriorityOrdered);
boolean p2 = (o2 instanceof PriorityOrdered);
if (p1 && !p2) {
return -1;
}
else if (p2 && !p1) {
return 1;
} // Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
int i1 = getOrder(o1);
int i2 = getOrder(o2);
// 比较的就是各自的order
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
// 结合Order
protected int getOrder(Object obj) {
return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : Ordered.LOWEST_PRECEDENCE );
} public static void sort(List<?> list) {
if (list.size() > 1) {
// 最后还是用着方法比较
Collections. sort(list, INSTANCE);
}
} public static void sort(Object[] array) {
if (array.length > 1) {
Arrays. sort(array, INSTANCE);
}
}
}
前面提到过,HandlerMapping 中的 getHandler返回的类型是:HandlerExecutionChain
HandlerExecutionChain 其实就是下面两:
private final Object handler ;
private HandlerInterceptor [] interceptors ;
HandlerInterceptor 就是拦截器,而HandlerExecutionChain 又是HandlerMapping 封装,所以在配置spring 拦截器的时候,我们只要把拦截器定义好然后注入到HandlerMapping 中,所有属于该map的handler都会使用到这些拦截器了,又根据上面多个mapping的机制,我们可以理解在多个mapping,而且url重复的时候,有可能一些优先级比较低的mapping中的拦截器就不会被执行到,这就是所谓spring没有完全全局的拦截器的由来。
所以可以说,spring的拦截器是定义在HandlerMapping 层面的,那么这就牵涉到粒度的问题了,前面说的多个HandlerMapping 的情况,还有特定handler的特殊拦截器配置就需要在代码对请求进行过滤实现了。
HandlerInterceptor 源码:
public interface HandlerInterceptor { // Called before the handler execution, returns a boolean value, “true” : continue the handler execution chain; “false”, stop the execution chain and return it.
boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception; void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception; void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception; }
网上解释一下:
(1 )preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法,顾名思义,该方法将在请求处理之前进行调用。SpringMVC 中的Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor 中的preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean类型的,当它返回为false 时,表示请求结束,后续的Interceptor 和Controller 都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。
(2 )postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法,由preHandle 方法的解释我们知道这个方法包括后面要说到的afterCompletion 方法都只能是在当前所属的Interceptor 的preHandle 方法的返回值为true 时才能被调用。postHandle 方法,顾名思义就是在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。postHandle 方法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor 的postHandle 方法反而会后执行,这和Struts2 里面的Interceptor 的执行过程有点类型。Struts2 里面的Interceptor 的执行过程也是链式的,只是在Struts2 里面需要手动调用ActionInvocation 的invoke 方法来触发对下一个Interceptor 或者是Action 的调用,然后每一个Interceptor 中在invoke 方法调用之前的内容都是按照声明顺序执行的,而invoke 方法之后的内容就是反向的。
(3 )afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法,该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的
总结:
1,order的设计值得借鉴
2,阅读源码可以升入理解拦截器的灵活使用。
让我们继续前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不会成功。
Spring mvc源码url路由-我们到底能走多远系列(38)的更多相关文章
- Bean实例化(Spring源码阅读)-我们到底能走多远系列(33)
我们到底能走多远系列(33) 扯淡: 各位: 命运就算颠沛流离 命运就算曲折离奇 命运就算恐吓着你做人没趣味 别流泪 心酸 更不应舍弃 ... 主题: Spring源码阅读还在继 ...
- 初始化IoC容器(Spring源码阅读)-我们到底能走多远系列(31)
我们到底能走多远系列(31) 扯淡: 有个问题一直想问:各位你们的工资剩下来会怎么处理?已婚的,我知道工资永远都是不够的.未婚的你们,你们是怎么分配工资的? 毕竟,对自己的收入的分配差不多体现了自己的 ...
- 精尽Spring MVC源码分析 - 寻找遗失的 web.xml
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - RequestToViewNameTranslator 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - ViewResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- Spring MVC源码——Root WebApplicationContext
目录 Spring MVC源码--Root WebApplicationContext 上下文层次结构 Root WebApplicationContext 初始化和销毁 ContextLoaderL ...
- spring mvc源码-》MultipartReques类-》主要是对文件上传进行的处理,在上传文件时,编码格式为enctype="multipart/form-data"格式,以二进制形式提交数据,提交方式为post方式。
spring mvc源码->MultipartReques类-> MultipartReques类主要是对文件上传进行的处理,在上传文件时,编码格式为enctype="multi ...
随机推荐
- Jsp页面中使用fckeditor控件的两种方法 [转]
fckeditor控件请到官方网站下载http://www.fckeditor.net,本例主要用到FCKeditor_2.6.3.zip.fckeditor-java-demo-2.4.1.zip. ...
- 使用MediaRecorder录制音频
手机一般都提供了麦克风硬件,而Android系统就可以利用该硬件来录制音频了. 为了在Android应用中录制音频,Android提供了MediaRecorder类,使用MediaRecorder录制 ...
- Android统计图表MPAndroidChart.
Android统计图表MPAndroidChart MPAndroidChart是在Android平台上开源的第三方统计图表库,可以绘制样式复杂.丰富的各种统计图表,如一般常见的折线图.饼状图.柱状图 ...
- iOS8 针对开发者所拥有的新特性汇总如下
iOS8 针对开发者所拥有的新特性汇总如下 1.支持第三方键盘 2.自带网页翻译功能(即在线翻译) 3.指纹识别功能开放:第三方软件可以调用 4.Safari浏览器可直接添加新的插件. 5.可以把一个 ...
- C语言中文件的读取和写入
在C语言中写文件 //获取文件指针 FILE *pFile = fopen("1.txt", //打开文件的名称 "w"); // 文件打开方式 如果原来有内容 ...
- C- printf的使用
ASC C之后引入的一个特性是,相邻的字符可以被自动连接 /* printf.cc * 2014/09/02 update */ #include <iostream> using nam ...
- java udp网络编程
import java.net.*; /* 通过UDP传输发送文字数据 1.建立socket服务 2.提供数据,并封装到数据包中 3.通过sokect服务的发送功能,将数据包发送出去 4.关闭资源 * ...
- AFNetworking、MKNetworkKit和ASIHTTPRequest对比
之前一直在使用ASIHTTPRequest作为网络库,但是由于其停止更新,iOS7上可能出现更多的问题,于是决定更换网络库. 目前比较流行的网络库主要有AFNetworking和MKNetworkKi ...
- c#图像处理入门(-bitmap类和图像像素值获取方法) 转
一.Bitmap类 Bitmap对象封装了GDI+中的一个位图,此位图由图形图像及其属性的像素数据组成.因此Bitmap是用于处理由像素数据定义的图像的对象.该类的主要方法和属性如下: 1. GetP ...
- Discuz 论坛的搭建(五)
配置discus论坛 1.下载discus论坛代码 2.解压缩到ApacheProject目录下 3.把discuz的upload文件copy到discuz文件夹下,然后删除upload文件夹 4.修 ...