本文中使用的Spring框架的版本为5.1前端
从设计上说,Spring Web MVC 使用前端控制器模式围绕一个中心Servlet
进行设计,这个中心Servlet
就是DispatcherServlet
,在DispatcherServlet
中提供了用于处理请求的通用逻辑,而具体工做委托给可配置的组件执行,经过这种模式使得Spring Web MVC框架变得很是灵活。java
与其它的Servlet
同样,DispatcherServlet
须要根据Servlet规范使用Java代码或者在web.xml
文件中进行配置。web
Spring Web MVC在启动时,最早加载DispatcherServlet
,而后DispatcherServlet
再根据配置加载请求映射、视图解析、异常处理所需的组件。spring
下面是在web.xml
文件中对DispatcherServlet
进行配置的示例:app
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
复制代码
在对请求进行处理时,DispatcherServlet
会把委托下面这些Bean对请求进行处理,以及给出适当的响应结果。框架
HandlerMappingide
将请求与handler
进行映射,HandlerMapping
有两个主要的实现:函数
RequestMappingHandlerMapping
SimpleUrlHandlerMapping
前者用于对@RequestMapping
注解提供支持,后者支持控制器的显示注册。源码分析
HandlerAdapter布局
帮助DispatcherServlet
调用与请求路径匹配的handler
去处理请求。
经过使用适配器的方式使DispatcherServlet
不用关心handler
的具体实现细节,好比:调用@Controller注解的控制器须要对该注解进行处理。
HandlerExceptionResolver
使用不一样的策略对异常进行处理,好比:返回的HTTP状态码是5xx仍是4xx。
ViewResolver
对视图进行解析,好比对JSP与FreeMarker的模版文件进行解析。
LocaleResolver, LocaleContextResolver
提供本地化支持,好比:多国语言、时区等。
ThemeResolver
提供主题支持,用于个性化布局。
MultipartResolver
用于解析multi-part
请求。
FlashMapManager
用于管理存放Falsh Attribute
的FlashMap
,Falsh Attribute
用于跨请求传递数据。
从源码中能够找到DispatcherServlet
类的定义以下:
public class DispatcherServlet extends FrameworkServlet 复制代码
能够看出,它继承自类FrameworkServlet
,下面咱们再来看一下类的总体继承关系:
从类的继承关系来看,最后会经过实现Servlet
接口来处理HTTP请求,其中与Servlet
有关系的类按继承顺序从上至下分别是:
Servlet
接口。GenericServlet
。HttpServlet
。HttpServlet
。FrameworkServlet
。其中GenericServlet
与HttpServlet
类只是简单对Servlet
接口作了一些封装与扩展,所以能够把分析的重点放在HttpSerlvetBean
、FrameworkServlet
与DispatcherServlet
这三个类上面。
在DispatcherServlet
初始化时,这三个类之间调用顺序以下图所示:
根据Servlet
规范,在Servlet
接口中会存在一个init()
方法,在一个Servlet
实被例化以后容器将会调用一次该方法。Spring Web MVC经过在HttpSerlvetBean
类中覆写init()
方法从而实现整个框架的加载。
在HttpSerlvetBean
类的init()
方法中主要作了两件事,一是从web.xml文件中读取初始化参数,好比:contextConfigLocation
参数。二是调用由子类实现的initServletBean()
方法完成具体的初始化工做。
init()
方法的实现以下:
@Override
public final void init() throws ServletException {
// 从web.xml文件中读取初始化参数
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 调用由子类实现的`initServletBean()`方法。
initServletBean();
}
复制代码
在上面这段代码中有点意思的是从web.xml
文件中读取初始化参数的方式,在这里咱们拿contextConfigLocation
参数来举例子。
对于参数contextConfigLocation
而言,在HttpServletBean
的子类FrameworkServlet
中存在一个具备如下定义的私有变量:
@Nullable
private String contextConfigLocation;
复制代码
以及对应的Get与Set方法:
public void setContextConfigLocation(@Nullable String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
@Nullable
public String getContextConfigLocation() {
return this.contextConfigLocation;
}
复制代码
可是Spring并不会直接调用setContextConfigLocation()
方法来给contextConfigLocation
变量赋值,而是由BeanWrapper
搭配ResourceEditor
来给变量赋值。
init()
方法在读取初始化参数以后,便会调用initServletBean()
方法来作初始化工做,该方法的在HttpServletBean
中是一个被protected
修饰的空方法,其定义以下:
protected void initServletBean() throws ServletException 复制代码
而具体的初始化工做则在HttpServletBean
的子类FrameworkServlet
中经过覆写initServletBean()
方法来完成。
initServletBean()
方法的实现以下:
protected final void initServletBean() throws ServletException {
try {
// 初始化WebApplicationContext
this.webApplicationContext = initWebApplicationContext();
// 一个Hook,让子类有机会在上下文初始化后作一些相关的工做
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
}
复制代码
在该方法中主要作了下面两件事:
initWebApplicationContext()
方法初始化webApplicationContext
。initFrameworkServlet()
方法让子类在初始化以后有机会作一些额外的工做。事实上,目前initFrameworkServlet()
是一个没有使用的空函数,而且子类也没有对它进行覆写,因此咱们只须要关注initWebApplicationContext()
方法便可。
initWebApplicationContext()
方法的实现以下:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 实例化时若是提供了webApplicationContext参数,则使用它。
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// 若是该上下文尚未进行过refresh则为它设置一个ID并进行refresh
if (cwac.getParent() == null) {
// 若是该的上下文没有父上下文则为它设置一个。
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 若是实例化时没有提供上下文,则查找ServletContext中有没有提供。
wac = findWebApplicationContext();
}
if (wac == null) {
// 若是上面没找到一个已经存在的上下文,则本身建立一个
wac = createWebApplicationContext(rootContext);
}
// 若是onRefresh尚未被调用,则手动调用一次
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
// 调用子类实现的onRefresh初始化策略对象
onRefresh(wac);
}
}
if (this.publishContext) {
// 将WebApplicationContext放入ServletContext之中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
复制代码
这个方法中主要作了下面这些事:
第一,检查是否是经过构造函数FrameworkServlet(WebApplicationContext webApplicationContext)
传入了webApplicationContext
参数。若是是,则会对传入的webApplicationContext
进行一些配置,好比:设置父上下文与上下文ID。
第二,检查ServletContext中有没有提供webApplicationContext
。若是有,拿过来直接使用。
第三,若是在第一步与第二步中都没有发现可用的webApplicationContext
,那就调用createWebApplicationContext()
方法本身建立一个。
第四,作一次兜底,经过refreshEventReceived
变量的值判断是否调用过onRefresh()
方法,若是从未调用过,则触发一次调用。
onRefresh
方法除了在initWebApplicationContext()
方法中调用了以外,在FrameworkServlet
中onApplicationEvent()
方法中也调用了onRefresh
方法。
onApplicationEvent()
方法的实现以下:
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
synchronized (this.onRefreshMonitor) {
onRefresh(event.getApplicationContext());
}
}
复制代码
在上面的代码中除了调用onRefresh()
方法以外,还给变量refreshEventReceived
赋值为真,确保onRefresh()
方法只会被调用一次。
那么问题来了,又是谁在调用onApplicationEvent()
方法?
对于这个问题咱们先来看一下用于建立webApplicationContext
的createWebApplicationContext()
方法的实现:
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
// 使用configLocation所指定的配置文件
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 对webApplicationContext进行配置
configureAndRefreshWebApplicationContext(wac);
return wac;
}
复制代码
在这个方法中,主要作了两件事:一是使用configLocation
所指定的配置文件来加载Bean,二是调用configureAndRefreshWebApplicationContext()
方法对webApplicationContext
进行配置。
若是你还有印象的话,你可能记得下面这段位于initWebApplicationContext()
方法中的代码也调用过 configureAndRefreshWebApplicationContext()
方法:
if (this.webApplicationContext != null) {
// 实例化时若是提供了webApplicationContext参数,则使用它。
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// 若是该上下文尚未进行过refresh则为它设置一个ID并进行refresh
if (cwac.getParent() == null) {
// 若是该的上下文没有父上下文则为它设置一个。
cwac.setParent(rootContext);
}
// 对webApplicationContext进行配置
configureAndRefreshWebApplicationContext(cwac);
}
}
}
复制代码
因而乎,咱们要研究一下在configureAndRefreshWebApplicationContext()
方法中作了哪些鲜为人知的事情。
configureAndRefreshWebApplicationContext()
方法的实现以下:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 若是上下文的ID原装的,则为其设置一个更具可读性的ID
// 使用配置文件指定的ID
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// 生成一个默认的ID
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
// 添加一个Listener用于监听webApplicationContext的refresh事件
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// 初始化PropertySource
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
// 调用ApplicationContextInitializer
applyInitializers(wac);
// 调用webApplicationContext的refresh方法
wac.refresh();
}
复制代码
在上面的代码中作了这么几件事:
在上面第二步中,ContextRefreshListener
类的实现以下:
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
复制代码
经过这个类的实现能够看出,这个类做为FrameworkServlet
的内部类在收到上下文已刷新的事件后会调用onApplicationEvent()
方法。
如今咱们已经能够前面的问题了:是谁在调用onApplicationEvent()
方法?。
经过上述的分析咱们能够知道在configureAndRefreshWebApplicationContext()
方法中对webApplicationContext
设置了一个监听器,这个监听器在监听到上下文refresh以后会调用onApplicationEvent()
方法。
既然onApplicationEvent()
方法与initWebApplicationContext()
都会调用onRefresh()
方法,那么在onRefresh()
方法中又作了哪些事情?
onRefresh()
方法在FrameworkServlet
中的定义以下:
protected void onRefresh(ApplicationContext context) {
}
复制代码
可见,它在FrameworkServlet
类中是一个空方法。具体逻辑由FrameworkServlet
的子类DispatcherServlet
覆写这个方法来实现,下文将对它的具体实现进行分析。
DispatcherServlet
类中onRefresh()
方法的实现以下:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
// 初始化解析multi-part请求须要的组件
initMultipartResolver(context);
// 初始化用于提供本地化支持的主键
initLocaleResolver(context);
// 初始化用于提供主题支持的组件
initThemeResolver(context);
// 初始化用于请求映射的组件
initHandlerMappings(context);
// 初始化用于对handler进行适配的组件
initHandlerAdapters(context);
// 初始化用于异常处理的组件
initHandlerExceptionResolvers(context);
// 初始化用于视图解析的组件
initRequestToViewNameTranslator(context);
initViewResolvers(context);
// 初始化用于管理Flash Attribute的组件
initFlashMapManager(context);
}
复制代码
经过上面的代码中能够看出,onRefresh()
方法将具体的工做委托给了initStrategies()
方法。
在initStrategies()
方法中,初始化了一系列的策略对象用于对不一样的功能提供支持,好比:请求映射、视图解析、异常处理等。
至此,DispatcherServlet
初始化完毕。
未完,待续。