SpringMvc源码-DispatcherServlet(一)

DispatcherServlet是springmvc最重要的组成部分,听名字就知道它负责一个请求到响应的流转,本篇主要讲DispatcherServlet的启动过程。
思考一个把问题?为什么DispatcherServlet初始化后,Spring的Ioc容器就会启动,容器中的Bean是什么时候注册到容器中的?

实际上整个SpringMvc的启动过程分为俩个阶段:

  1. ContextLoaderListener初始化,实例化IOC容器,并将此容器注册到ServletContext中。
  2. DispatcherServlet初始化,建立自己的上下文,也注册到ServletContext中。

    见下面这段web.xml的配置(springboot不在我们这篇文章讨论之列。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">

<display-name>ota</display-name>
<description>ota web application</description>

<!-- 系统组件加载顺序:context-param -> listener -> filter -> servlet -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- GZip -->
<filter>
<filter-name>gzipFilter</filter-name>
<filter-class>com.travelsky.ibeplus.compress.Compress2WayFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>gzipFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- Servlet that dispatches request to registered handlers (Controller implementations). -->
<servlet>
<servlet-name>ota</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/mvc-core-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<!-- 需要定义在对应的servlet之后 -->
<servlet-mapping>
<servlet-name>ota</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>

ContextLoaderListener初始化

见web.xml这一段

1
2
3
4
5
6
7
8
 <context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

关键逻辑:
ContextLoaderListener采用了很典型的适配器模式,继承了ContextLoader,实现了ServletContextListener接口。
在contextInitialized()->initWebApplicationContext()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
......

try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown. 初始化webApplication
......
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 该方法会调用setConfigLocation,将xml中的contextConfigLocation传递给WebApplicationContext,还会调用refresh方法执行容器的bean初始化等操作
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将ConfigurableWebApplicationContext设置会servletContext
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}

......
return this.context;
} catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}

configureAndRefreshWebApplicationContext方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
//关键1
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}

//关键2 设置servletContext 和web.xml的configLocationParam 传给 ConfigurableWebApplicationContext
wac.setServletContext(getServletContext());
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}

// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}

customizeContext(sc, wac);
//关键3 见ApplicationContext.refresh方法 初始化容器的bean
wac.refresh();
}

DispatcherServlet初始化

相关的xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--  Servlet that dispatches request to registered handlers (Controller implementations).  -->
<servlet>
<servlet-name>ota</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/mvc-core-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<!-- 需要定义在对应的servlet之后 -->
<servlet-mapping>
<servlet-name>ota</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

类的继承关系:DispatcherServlet–>FrameworkServlet–>HttpServletBean–>HttpServlet

HttpServletBean重写了Serlvet的init方法,封装了spring的web容器启动的过程。

HttpServletBean

init()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@Override
public final void init() throws ServletException {

// Set bean properties from init parameters.
//1. 获取web.xml中的getInitParameterNames,将servletConfig的initParam参数的key和value都保存到pvs中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//BeanWrapper实际是BeanWrapperImpl【DispatcherServlet】
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
//通过BeanWrapper为bean【DispatcherServlet】赋值
//BeanWrapper的target的属性的key要和web.xml中的<init-param>的param-name相等,这里就给DispatchServlet的contextConfigLocation赋值了
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}

// Let subclasses do whatever initialization they like.
// FrameworkServlet实现了initServletBean方法
initServletBean();
}

FrameworkServlet

FrameworkServlet的Override了HttpServletBean的initServletBean方法,在该方法通过initWebApplicationContext初始化了webApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();

try {
//关键方法 在这里初始化了 webApplicationContext
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}

if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}

if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}

FrameworkServlet.initWebApplicationContext()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
protected WebApplicationContext initWebApplicationContext() {
//ContextLoaderListerner中已经将context放在了ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;

//以下是初始化webApplicationContext的过程
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
//关键方法
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
//关键方法 上面如果为空,createWebApplicationContext里会在执行configureAndRefreshWebApplicationContext
wac = createWebApplicationContext(rootContext);
}

if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
// DispatcherServlet实现了onRefresh-->即:initStrategies(context);
onRefresh(wac);
}
}

if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}

return wac;
}

重点方法,FrameworkServlet.configureAndRefreshWebApplicationContext(),初始化ConfigurableWebApplicationContext并且执行refresh。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}

wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
//将ContextRefreshListener注册到ApplicationContext中
//将ContextRefreshListener注册到ApplicationContext中
//在调用wac.refresh时候会回触发spring框架中的的onApplicationEventContext
// SourceFilteringListener.onApplicationEventContext-->RefreshListener.onApplicationEvent-->FrameworkServlet.this.onApplicationEvent-->DipatcherServlet.onRefresh
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}

postProcessWebApplicationContext(wac);
applyInitializers(wac);

//applicationContext.refresh()-->registerListener会触发ContextRefreshListener.onApplicationEvent
wac.refresh();
}

DispatcheServlet

重点方法onRefresh:逻辑见下图,重点方法initStrategies。组件的优先级:自己注入的Bean>DespatchServlet.properties中的Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}

/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
* 初始化组件
*/
protected void initStrategies(ApplicationContext context) {
//MultipartResolver,不注册不支持上传文件,默认是StandardServletMultipartResolver,需要自己注册
initMultipartResolver(context);
//i18n 默认调用getDefaultStrategy(),见:DispatchServlet.properties,org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
initLocaleResolver(context);
//主题 默认调用getDefaultStrategy(),见:DispatchServlet.properties,org.springframework.web.servlet.theme.FixedThemeResolver
initThemeResolver(context);
//重要组件,detectAllHandlerMappings?从context包含父类取所有的HandlerMapping or 取一个,为空从DispatchServlet.properties
initHandlerMappings(context);
//重要组件,detectAllHandlerMappings?从context包含父类取所有的HandlerAdapter or 取一个,为空从DispatchServlet.properties
initHandlerAdapters(context);
//异常处理,同上类似的逻辑
initHandlerExceptionResolvers(context);
//Request->ViewName
initRequestToViewNameTranslator(context);
//ViewResolver
initViewResolvers(context);
//flashMapManager
initFlashMapManager(context);
}

总结

这里的context默认是XmlWebApplicationContext,见contextLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//文件内容:org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

//静态方法代码块
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}

见DispatcherServlet

1
2
3
4
5
6
7
8
9
10
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//getContextClass返回值是DEFAULT_CONTEXT_CLASS
Class<?> contextClass = getContextClass();
......
configureAndRefreshWebApplicationContext(wac);

return wac;
}

启动时候如何加载的init-param,见HttpServletBean的代码,推荐下去试试BeanWrapper

1
2
3
4
5
6
7
//BeanWrapper实际是BeanWrapperImpl【DispatcherServlet】
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
//为BeanWrapper赋值,init-Param的name对应的是bw的属性。。。这段不仔细看真看不出来 initParam怎么赋值的
bw.setPropertyValues(pvs, true);