spring源码 — 四、MVC
spring mvc是怎么实现的?为什么我们只需要在方法上写一个注解,就可以通过http访问这个接口?下面我们分3部分来解答这两个问题
注意:本文是基于spring4.3.2的
- spring mvc整体流程
- HandlerMapping
- HandlerAdapter
spring mvc整体流程
我们通过看一下spring处理一个http请求的过程来大概了解下

Spring mvc的入口就是DispatcherServlet,请求交给这个servlet之后,通过调用doDispatch来分发这个请求,主要做了一下几件事情
- 处理multipart请求,如果有文件上传等请求,先封装为DefaultMultipartHttpServletRequest
- 从HandlerMapping中获取处理该请求的handler,并构造HandlerExecutionChain,将入所有的interceptor
- 根据handler从所有的HandlerAdapter中找到可以处理这个handler的adapter
- 执行所有interceptor.prehandle方法
- 调用写有RequestMapping注解的方法,返回ModelAndView
- 执行所有interceptor.postHandle方法
- 解析view
- render,将model转化为response返回
- 执行所有interceptor.afterCompletion
- cleanupMultipart
从上面的流程中可以看出,在处理过程中主要是一些关键组件完成了对应的逻辑,下面我们看下其中的组件。
HandlerMapping
作为DispatcherServlet组件之一,就是其中一个field
org.springframework.web.servlet.DispatcherServlet#handlerMappings
主要作用是维护从url到Controller的映射,根据url找到对应的Controller。
有哪些HandlerMapping
spring是怎么查找所有的HandlerMapping的呢?是在DispatcherServlet初始化的时候查找并初始化这个字段的
// org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
// 从容器中查找所有HandlerMapping类型的bean
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.
AnnotationAwareOrderComparator.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");
}
}
}
那么容器中有哪些HandlerMapping类型的bean呢?如果我们使用RequestMapping注解的话需要在xml中进行以下配置
<mvc:annotation-driven />
spring解析这个标签使用的是
org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
这个类解析xml标签的时候会向容器中注册RequestMappingHandlerMapping,这个类继承自HandlerMapping
如果我们进行了一下配置
<mvc:default-servlet-handler />
spring解析这个标签的使用的是
org.springframework.web.servlet.config.DefaultServletHandlerBeanDefinitionParser
解析的时候spring会向容器注入DefaultServletHttpRequestHandler和SimpleUrlHandlerMapping
那么
- BeanNameHandlerMapping
- DefaultAnnotationHandlerMapping
所以这种情况下会有这三个HandlerMapping类型的bean,spring遍历handlerMappings来根据request path查找对应的handler。
HandlerMapping怎么管理url到handler的映射关系
HandlerMapping抽象类AbstractHandlerMethodMapping中有一个field
// org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
private final MappingRegistry mappingRegistry = new MappingRegistry();
这个类就是用来维护url到handler的映射,看下这个类有哪些数据结构
// org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry
// 所有的mapping都会加入这个map中
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();
// 只有具体的(没有通配符org.springframework.util.AntPathMatcher#isPattern)的才会加入这个map中
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();
// 根据name找handler
private final Map<String, List<HandlerMethod>> nameLookup =
new ConcurrentHashMap<String, List<HandlerMethod>>();
// cors mapping
private final Map<HandlerMethod, CorsConfiguration> corsLookup =
注册mapping的过程就是讲找到的mapping添加到上面对应的数据结构中,以RequestMappingHandlerMapping为例,具体注册的时机是在RequestMappingHandlerMapping bean初始化的时候,spring容器初始化完成以后会将容器中eagere init的bean进行初始化,构造bean的时候会调用afterPropertiesSet,最后会调用AbstractHandlerMethodMapping#initHandlerMethods来查找容器中所有的bean
- 找到容器中所有的bean
- 针对每个bean查找里面有没有mapping
在调用到initHandlerMethods方法的时候会拿出容器中所有的bean,用isHandler判断是否是handler,其实就是判断是否有Controller或RequestMapping的注解
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
找到类后,再找里面的方法,方法的查找逻辑就是看看方法上有没有RequestMapping,
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
// 查找方法上的RequestMapping注解
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class<?> ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
找到对应的方法之后构造成mapping注册到MappingRegistry
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
// 所有的mapping都会加入mappingLookup
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
// 找到所有具体的(直接的,就是不包含通配符)的url添加到urlLookup
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
在根据url查找handler的时候优先查找urlLookup,看看没有通配符的能不能匹配,如果没有直接匹配的则再从mappingLookup里面找
HandlerAdapter
HandlerAdapter是为了将invoke过程的细节对于DispatcherServlet屏蔽,比如参数解析。
在DispatcherServlet初始化的时候调用inithandlerAdapters,在里面找容器中所有的handlerAdapter实现类,容器里面的handlerAdapter是在解析mvc标签的时候加入容器的
总结
这篇文章通过spring处理一次请求的过程了解了springmvc怎么工作,以及其中两个关键组件,HandlerMapping和HandlerAdapter。
spring源码 — 四、MVC的更多相关文章
- Spring源码深度解析之Spring MVC
Spring源码深度解析之Spring MVC Spring框架提供了构建Web应用程序的全功能MVC模块.通过策略接口,Spring框架是高度可配置的,而且支持多种视图技术,例如JavaServer ...
- Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)
前言 在上一篇<Spring学习之——手写Spring源码(V1.0)>中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也 ...
- 【spring源码分析】IOC容器初始化(四)
前言:在[spring源码分析]IOC容器初始化(三)中已经分析了BeanDefinition注册之前的一些准备工作,下面将进入BeanDefinition注册的核心流程. //DefaultBean ...
- Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md
写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...
- Spring源码分析(二十四)初始化非延迟加载单例
摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 完成BeanFactory的初始化工作,其中包括ConversionS ...
- Spring源码分析(十四)从bean的实例中获取对象
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 在getBean方法中,getObjectForBeanlnstance ...
- Spring源码分析(四)容器的基础XmlBeanFactory
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 经过Spring源码分析(二)容器基本用法和Spring源码分析(三)容 ...
- Spring源码系列(四)--spring-aop是如何设计的
简介 spring-aop 用于生成动态代理类(底层是使用 JDK 动态代理或 cglib 来生成代理类),搭配 spring-bean 一起使用,可以使 AOP 更加解耦.方便.在实际项目中,spr ...
- 阅读spring源码
读Spring源码之前,你要先清楚,为什么你要用Spring... Spring最基本的功能是做为管理bean的容器,所以我以为应该先从org.springframework.context包了解咯, ...
随机推荐
- HTML学习笔记:1.基础概念
①HTML:Hypertext Markup Language,即超文本标记语言,文件由标记组成 ②HTML发展史 (几个重要节点): 1993(IETF):HTML 1.0 1995(W3C): ...
- Java 核心卷学习笔记(一)
Java基程序设计结构 1.注释 三种注释方式: // 注释单行 /* 内容 */ 注释单行 /** * 内容 */
- Robot Framework自动化_Selenium2Library 关键字
Robot Framework自动化_Selenium2Library 关键字 培训老师:肖能尤 2016/06/12 课程目的 一.Robot framework Selenium2Library ...
- 实验效果展示(会声会影+FSCapture)
第一步,视频录制: 利用屏幕录制软件(Eg:FSCapture,可设定矩形区域)录制信号采集过程,存储. 第二步,视频叠加制作 1)导入视频 2)主轨,复叠轨视频安插&时序调整 3)两个视频图 ...
- 项目开发中如何规范自己的CSS
1.CSS规范 - 分类方法 CSS文件的分类和引用顺序 通常,一个项目我们只引用一个CSS,但是对于较大的项目,我们需要把CSS文件进行分类. 我们按照CSS的性质和用途,将CSS文件分成“公共型样 ...
- 小黄鸡机器人和小I机器人的调用
<?php //---------------------------------聊天小机器人类---------------------------------------------- ...
- underscore源码阅读
1:函数式编程初探 2:if(obj.length === +obj.length)的判断 3:JS对象属性用中括号表示的优点在于可以用变量表示属性 4:callee,caller---JS高级程序设 ...
- IDEA破解 Intellij IDEA license server 激活(可用)
激活地址如下图所示: 2018 intellij idea 注册码(亲测可用): C0FHYYCJ22-eyJsaWNlbnNlSWQiOiJDMEZIWVlDSjIyIiwibGljZW5zZWVO ...
- eclipse下搭建hibernate5.0环境
hibernate引入的jar包:hibernate-release-5.0.12.Final.zip 数据库驱动:mysql-connector-java-5.1.46 二.安装hibernate插 ...
- C++中“wchar_t* ”和“ char * ”之间的相互转换
把char*转换为wchar_t* 用stdlib.h中的mbstowcs_s函数,可以通过下面的例子了解其用法: char *CStr = "string to convert" ...