SpringBoot之DispatcherServlet详解及源码解析
在使用SpringBoot之后,我们表面上已经无法直接看到DispatcherServlet的使用了。本篇文章,带大家从最初DispatcherServlet的使用开始到SpringBoot源码中DispatcherServlet的自动配置进行详解。
DispatcherServlet简介
DispatcherServlet是前端控制器设计模式的实现,提供了Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring Ioc容器无缝集成,从而可以获得Spring的所有好处。
DispatcherServlet作用
DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:
- 文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
- 通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
- 通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);
- 通过ViewResolver解析逻辑视图名到具体视图实现;
- 本地化解析;
- 渲染具体的视图等;
- 如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。
DispatcherServlet工作流程
DispatcherServlet传统配置
DispatcherServlet作为前置控制器,通常配置在web.xml文件中的。拦截匹配的请求,Servlet拦截匹配规则要自已定义,把拦截下来的请求,依据相应的规则分发到目标Controller来处理,是配置spring MVC的第一步。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcherServlet-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
DospatcherServlet实际上是一个Servlet(它继承HttpServlet)。DispatcherServlet处理的请求必须在同一个web.xml文件里使用url-mapping定义映射。这是标准的J2EE servlet配置。
在上述配置中:
- servlet-name用来定义servlet的名称,这里是dispatcherServlet。
- servlet-class用来定义上面定义servlet的具体实现类,这里是org.springframework.web.servlet.DispatcherServlet。
- init-param用来定义servlet的初始化参数,这里指定要初始化WEB-INF文件夹下的dispatcherServlet-servlet.xml。如果spring-mvc.xml的命名方式是前面定义servlet-name+"-servlet",则可以不用定义这个初始化参数,(Spring默认配置文件为“/WEB-INF/[servlet名字]-servlet.xml”),Spring会处理这个配置文件。由此可见,Spring的配置文件也可放置在其他位置,只要在这里指定就可以了。如果定义了多个配置文件,则用“,”分隔即可。
- servlet-mapping定义了所有以.do结尾的请求,都要经过分发器。
当DispatcherServlet配置好后,一旦DispatcherServlet接受到请求,DispatcherServlet就开始处理请求了。
DispatcherServlet处理流程
当配置好DispatcherServlet后,DispatcherServlet接收到与其对应的请求之时,处理就开始了。处理流程如下:
找到WebApplicationContext并将其绑定到请求的一个属性上,以便控制器和处理链上的其它处理器能使用WebApplicationContext。默认的属性名为DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE。
将本地化解析器绑定到请求上,这样使得处理链上的处理器在处理请求(准备数据、显示视图等等)时能进行本地化处理。如果不需要本地化解析,忽略它就可以了。
将主题解析器绑定到请求上,这样视图可以决定使用哪个主题。如果你不需要主题,可以忽略它。
如果你指定了一个上传文件解析器,Spring会检查每个接收到的请求是否存在上传文件,如果是,这个请求将被封装成MultipartHttpServletRequest以便被处理链中的其它处理器使用。(Spring's multipart (fileupload) support查看更详细的信息)
找到合适的处理器,执行和这个处理器相关的执行链(预处理器,后处理器,控制器),以便为视图准备模型数据。
如果模型数据被返回,就使用配置在WebApplicationContext中的视图解析器显示视图,否则视图不会被显示。有多种原因可以导致返回的数据模型为空,比如预处理器或后处理器可能截取了请求,这可能是出于安全原因,也可能是请求已经被处理过,没有必要再处理一次。
DispatcherServlet相关源码
org.springframework.web.servlet.DispatcherServlet中doService方法部分源码:
protected void doService(HttpServletRequest request,
HttpServletResponse response) throws Exception {
// ......
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,getWebApplicationContext());
// ......
}
通过上面源码得知,DispatcherServlet会找到上下文WebApplicationContext(其指定的实现类为XmlWebApplicationContext),并将它绑定到一个属性上(默认属性名为WEB_APPLICATION_CONTEXT_ATTRIBUTE),以便控制器能够使用WebApplicationContext。
initStrategies方法源码如下:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
从如上代码可以看出,DispatcherServlet启动时会进行我们需要的Web层Bean的配置,如HandlerMapping、HandlerAdapter等,而且如果我们没有配置,还会给我们提供默认的配置。
DispatcherServlet SpringBoot自动配置
DispatcherServlet在Spring Boot中的自动配置是通过DispatcherServletAutoConfiguration类来完成的。
先看注解部分代码:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
...
}
@AutoConfigureOrder指定该自动配置的优先级;@Configuration指定该类为自动配置类;@ConditionalOnWebApplication指定自动配置需要满足是基于SERVLET的web应用;@ConditionalOnClass指定类路径下必须有DispatcherServlet类存在;@AutoConfigureAfter指定该自动配置必须基于ServletWebServerFactoryAutoConfiguration的自动配置。
DispatcherServletAutoConfiguration中关于DispatcherServlet实例化的代码如下:
@Configuration(proxyBeanMethods = false) // 实例化配置类
@Conditional(DefaultDispatcherServletCondition.class) // 实例化条件:通过该类来判断
@ConditionalOnClass(ServletRegistration.class) // 存在指定的ServletRegistration类
// 加载HttpProperties和WebMvcProperties
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
// 创建DispatcherServlet
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// 初始化DispatcherServlet各项配置
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
return dispatcherServlet;
}
// 初始化上传文件的解析器
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
内部类DispatcherServletConfiguration同样需要满足指定的条件才会进行初始化,具体看代码中的注释。
其中的dispatcherServlet方法中实现了DispatcherServlet的实例化,并设置了基础参数。这对照传统的配置就是web.xml中DispatcherServlet的配置。
另外一个方法multipartResolver,用于初始化上传文件的解析器,主要作用是当用户定义的MultipartResolver名字不为“multipartResolver”时,通过该方法将其修改为“multipartResolver”,相当于重命名。
其中DispatcherServletConfiguration的注解@Conditional限定必须满足DefaultDispatcherServletCondition定义的匹配条件才会自动配置。而DefaultDispatcherServletCondition类同样为内部类。
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DefaultDispatcherServletCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
List<String> dispatchServletBeans = Arrays
.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
return ConditionOutcome
.noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
return ConditionOutcome.noMatch(
message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
if (dispatchServletBeans.isEmpty()) {
return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());
}
return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans")
.items(Style.QUOTE, dispatchServletBeans)
.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
}
该类的核心功能,总结起来就是:检验Spring容器中是否已经存在一个名字为“dispatcherServlet”的DispatcherServlet,如果不存在,则满足条件。
在该自动配置类中还有用于实例化ServletRegistrationBean的内部类:
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
// 通过ServletRegistrationBean将dispatcherServlet注册为servlet,这样servlet才会生效。
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
// 设置名称为dispatcherServlet
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// 设置加载优先级,设置值默认为-1,存在于WebMvcProperties类中
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
DispatcherServletRegistrationConfiguration类的核心功能就是注册dispatcherServlet使其生效并设置一些初始化的参数。
其中,DispatcherServletRegistrationBean继承自ServletRegistrationBean,主要为DispatcherServlet提供服务。DispatcherServletRegistrationBean和DispatcherServlet都提供了注册Servlet并公开DispatcherServletPath信息的功能。
Spring Boot通过上面的自动配置类就完成了之前我们在web.xml中的配置操作。这也是它的方便之处。
参考文章:
https://www.cnblogs.com/wql025/p/4805634.html
https://juejin.im/post/5d3066736fb9a07ece6806e4
原文链接:《SpringBoot之DispatcherServlet详解及源码解析》
Spring技术视频
CSDN学院:《Spring Boot 视频教程全家桶》
程序新视界:精彩和成长都不容错过

SpringBoot之DispatcherServlet详解及源码解析的更多相关文章
- 详解HashMap源码解析(下)
上文详解HashMap源码解析(上)介绍了HashMap整体介绍了一下数据结构,主要属性字段,获取数组的索引下标,以及几个构造方法.本文重点讲解元素的添加.查找.扩容等主要方法. 添加元素 put(K ...
- AFNetworking 3.0 使用详解 和 源码解析实现原理
AFN原理&& AFN如何使用RunLoop来实现的: 让你介绍一下AFN源码的理解,首先要说说封装里面主要做了那些重要的事情,有那些重要的类(XY题) 一.AFN的实现步骤: NSS ...
- CharacterEncodingFilter详解及源码解析
字符编码过滤器 (Spring框架对字符编码的处理) 基于函数回调,对所有请求起作用,只在容器初始化时调用一次,依赖于servlet容器. web.xml配置文件 <filter> &l ...
- cas客户端流程详解(源码解析)--单点登录
博主之前一直使用了cas客户端进行用户的单点登录操作,决定进行源码分析来看cas的整个流程,以便以后出现了问题还不知道是什么原因导致的 cas主要的形式就是通过过滤器的形式来实现的,来,贴上示例配置: ...
- 六张图详解LinkedList 源码解析
LinkedList 底层基于链表实现,增删不需要移动数据,所以效率很高.但是查询和修改数据的效率低,不能像数组那样根据下标快速的定位到数据,需要一个一个遍历数据. 基本结构 LinkedList 是 ...
- 详解HashMap源码解析(上)
jdk版本:1.8 数据结构: HashMap的底层主要基于数组+链表/红黑树实现,数组优点就是查询块,HashMap通过计算hash码获取到数组的下标来查询数据.同样也可以通过hash码得到数组下标 ...
- Ros学习——移动机器人Ros导航详解及源码解析
1 执行过程 1.运行仿真机器人fake_turtlebot.launch:加载机器人模型——启动机器人仿真器——发布机器人状态 2.运行amcl节点fake_amcl.launch:加载地图节点ma ...
- 详解ConCurrentHashMap源码(jdk1.8)
ConCurrentHashMap是一个支持高并发集合,常用的集合之一,在jdk1.8中ConCurrentHashMap的结构和操作和HashMap都很类似: 数据结构基于数组+链表/红黑树. ge ...
- 基于双向BiLstm神经网络的中文分词详解及源码
基于双向BiLstm神经网络的中文分词详解及源码 基于双向BiLstm神经网络的中文分词详解及源码 1 标注序列 2 训练网络 3 Viterbi算法求解最优路径 4 keras代码讲解 最后 源代码 ...
随机推荐
- angularjs通过ng-bind-html指令和$sce服务绑定html
代码: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8 ...
- 基于Java的开源爬虫框架WebCollector的使用
一.WebCollector介绍 WebCollector是一个无须配置.便于二次开发的JAVA爬虫框架(内核),它提供精简的的API,只需少量代码即可实现一个功能强大的爬虫. WebCollecto ...
- jwt token
1 ,session 认证机制: ,用户登录,传递用户名和密码给客户端 ,服务器进行用户名和密码的校验,如果校验成功,将用户保存到session ,将sessionid通过cookie返回给客服端,客 ...
- 百度地图Javascript API 调用示例
调用示例 !<!DOCTYPE html> <html> <head> <title>百度地图DEMO</title> </head& ...
- CTR@模型整体架构
1. 推荐系统 1.1 总体框架 主要包括:样本选择.数据清洗.特征提取与选择.模型训练.在线预估和排序. 1.2 目的 帮助用户找到想要的商品,挖掘数据分布. 降低信息过载. 提高站点的点击率/转化 ...
- [Java] 生成二维码源码,可以在二维码中间加logo,底部可以加文字介绍
链接:https://pan.baidu.com/s/1bc1h-ix-No-2o9Ysd4_B3Q提取码:0ad4
- 转:nginx和php-fpm的两种通信方式
原文地址:https://segmentfault.com/q/1010000004854045 Nginx和PHP-FPM的进程间通信有两种方式,一种是TCP,一种是UNIX Domain Sock ...
- Python安装cx_Oracle与操作数据测试小结
这里简单总结一下Python操作Oracle数据库这方面的相关知识.只是简单的整理一下之前的实验和笔记.这里的测试服务器为CentOS Linux release 7.5. 个人实验.测试.采集数据的 ...
- GPS学习笔记
预备知识 1. 卫星发送信号时,是以特定频率的电磁波为载波,调整信号到载波上的.多普勒效应就是,卫星和接收机是相对运动的,那么载波的频率会随运动距离发生改变.(<GPS测量与数据处理>p3 ...
- day3(数论)
总得来说,这是可怕的一天,极其可怕的一天(完) 一.数论 阴影啊! 首先,设ab为两个整数,则存在唯一的q和r,使得a=qb+r 若r=0,则b整除a,记作b|a. (1)同余 若a/m和b/m的余数 ...