Spring源码分析(7)——SpringMVC的启动过程

SpringMVC+Spring是常用的Java框架,但SpringMVC有着和Spring完全不同的启动过程。

一、SpringMVC的启动文件

1、SpringMVC是一个Servlet,而普通web应用的启动文件便是WEB-INF/web.xml,web.xml的读取顺序大致是:context-param >> listener >> filter >> servlet

2、context-param和listener是一对,用于初始化Spring根容器,配置文件是applicationContext.xml

3、servlet和servlet-mapping是另一对,用于初始化SpringMVC容器,配置文件是servletContext.xml

4、这个web.xml文件配置将会初始化两个Spring容器,SpringMVC容器是Spring根容器的子容器。所以一般Controller一般配置在SpringMVC容器中,Service和DAO配置在Spring容器中,可以实现Spring和MVC层的解耦。

web.xml
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
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>

<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>

<servlet>
<servlet-name>SpringMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:servletContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>SpringMvc</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>

二、SpringMVC的启动过程

1、先回顾一下web.xml文件的加载过程:context-param >> listener >> filter >> servlet

2、context-param配置的读取是由容器完成的,可以不用关心,Tomcat容器会将context-param的值作为参数传给listener

3、Tomcat容器会加载web.xml中的listener,这里配置的listener是ContextLoaderListener,它实现了sevlet提供的ServletContextListener接口,其中里面的contextInitialized便是Tomcat容器启动时首先调用的方法。

这段代码主要做了以下几件事:

1)WebApplicationContext存在性的验证,如果已经有了就抛出异常

2)创建WebApplicationContext实例,如果没有指定容器类型,这个容器默认类型是XmlWebApplicationContext。来源是ContextLoader.properties这个配置文件的默认配置。

3)将配置文件applicationContext.xml设置到XmlWebApplicationContext容器中,调用容器的refresh方法来初始化容器。(是不是很熟悉,refresh方式是ApplicationContext容器的标准初始化入口,用于注册后置处理器、实例化bean等一切Spring容器有关的工作)

4)将XmlWebApplicationContext容器记录到servletContext中

ContextLoaderListener.java
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
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// WebApplicationContext存在性的验证,如果已经有了就抛出异常
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}

servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();

try {
if (this.context == null) {
// 创建WebApplicationContext实例
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 初始化容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将XmlWebApplicationContext容器记录到servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
......
}

4、由于没有配置filter,就不说filter的启动了,道理和listener类似,无非是实现一个接口,Tomcat容器会自动调用罢了。

5、这里配置了一个servlet,实现类是DispatcherServlet,这是SpringMVC启动的核心。DispatcherServlet实现了Servlet接口,其中Tomcat容器启动时会首先调用init方法来初始化,这也是SpringMVC启动的初始化方法。

在这个方法的调用栈中,我们最关心的无非是initWebApplicationContext方法,这个方法会再创建一个XmlWebApplicationContext容器,并初始化SpringMVC的九大组件,来看看具体做了哪些事:

1)寻找或创建一个新的SpringMVC容器,这个容器的类型也是XmlWebApplicationContext

2)将刚才创建的Spring容器设置为SpringMVC容器的父容器,并调用refresh来初始化容器并实例化servletContext.xml配置文件中的所有bean

3)调用onRefresh方法初始化SpringMVC的九大组件

4)将XmlWebApplicationContext容器记录到servletContext中

HttpServletBean.java
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
48
49
50
51
52
53
54
55
56
57
58
@Override
public final void init() throws ServletException {
......
initServletBean();
}

@Override
protected final void initServletBean() throws ServletException {
......
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
......
}

protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;

// 如果已经创建了容器
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 将刚才创建的Spring容器设置为SpringMVC容器的父容器
cwac.setParent(rootContext);
}
// 初始化容器,调用refresh来初始化容器并实例化servletContext.xml配置文件中的所有bean
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
// 新创建容器,并初始化
wac = createWebApplicationContext(rootContext);
}

if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {

onRefresh(wac);
}
}

if (this.publishContext) {
// 将XmlWebApplicationContext容器记录到servletContext中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}

三、SpringMVC启动流程图

不管是Listener、Filter还是Servlet,都是实现了一个Java提供的接口,而Tomcat容器启动时会按顺序调用他们的init方法来初始化。

SpringMVC正是在Servlet的基础实现了一些接口,达到了启动Spring容器的目的,如果看流程图还是比较简单的,但看代码却是一个十分伤脑筋的事情!

创建父容器
WebApplicationContext
ContextLoaderListener
refresh
初始化容器
容器记录到
servletContext
再创建一个子容器
WebApplicationContext
DispatcherServlet
refresh
初始化容器
容器记录到
servletContext
读取
context-param
初始化filter
(略过)
onRefresh
初始化九大组件

http://zhanjindong.com/assets/pdf/Servlet3.1-Specification.pdf

https://www.jianshu.com/p/7097fea8ce3f

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×