上一阶段,分析了 HttpServletBean 在初始化阶段中做的事情,它最后留了一个 initServletBean() 模板方法来交给子类 FrameworkServlet 继续初始化。
所以进入源码:
FrameworkServlet # initServletBean()
@Override
protected final void initServletBean() throws ServletException {
//...
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (...) {
//...
}
//...
}
核心代码只有两句:
初始化 WebApplicationContext :this.webApplicationContext = initWebApplicationContext(); (创建并初始化 SpringWeb 容器)
初始化 initFrameworkServlet :initFrameworkServlet() 模板方法,留给子类实现(尚未发现有子类实现此方法,所以没有做事情,不用分析)
创建并初始化 SpringWeb容器:initWebApplicationContext()
先看注释:
protected WebApplicationContext initWebApplicationContext() {
/*
1、获取Spring根容器 rootContext
*/
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
/*
如果在 FrameworkServlet 的构造方法中设置了webApplicationContext,则会使用这个webApplicationContext作为SpringWeb容器(在JavaConfig的配置情况下,可能会进入这个if)
(注:在此方法执行前,this.webApplicationContext只会在构造方法中被赋予值(且没有set方法,无法通过web.xml配置被赋值))
*/
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
/*
2、查看是否在 ServletContext 中配置了 SpringWeb 容器(通常不会进入这个if)
*/
if (wac == null) {
wac = findWebApplicationContext();
}
/*
3、如果仍未找到,则直接创建一个 SpringWeb 容器 (一般会进入这个if)
*/
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
/*
4、调用onRefresh方法,具体实现在DispatcherServlet中(因为监听机制,此方法一定会被调用,且只会被调用一次)
注意:这里刷新的是子容器,即 SpringWeb 容器
*/
if (!this.refreshEventReceived) {
onRefresh(wac);
}
/*
根据publishContext标志位(默认为true),决定是否将SpringWeb容器,添加到ServletContext中
*/
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
其中比较重要的代码分为 4 个部分,下面来分部分来讲解,每个部分都做了什么。
1、获取Spring根容器rootContext
此部分,是尝试从 ServletContext 中获取 Spring 根容器,方便后续将其设置为 SpringWeb 容器的父容器(看上一篇文章)
默认情况下,会将Spring 根容器设置在 ServletContext 的属性中(Attribute)(看上一篇文章),默认根容器的 key 为 org.springframework.web.context.WebApplicationContext.ROOT(可以查看WebApplicationContext类源码)
获取时,调用 ServletContext.getAttribute(“org.springframework.web.context.WebApplicationContext.ROOT”) ,就可以获取根容器了
WebApplicationContextUtils # getWebApplicationContext
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); //根据 org.springframework.web.context.WebApplicationContext.ROOT 查找
}
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
// 获取 ServletContext 中 key 为 org.springframework.web.context.WebApplicationContext.ROOT 的内容
Object attr = sc.getAttribute(attrName);
// 如果没有在web.xml中配置监听器 ContextLoaderListener,则根容器为null
if (attr == null) {
return null;
}
//...
return (WebApplicationContext) attr;
}
可以看到,如果没有在web.xml中配置监听器 ContextLoaderListener ,那么父容器就设置为null
2、从ServletContext中决定 SpringWeb 容器
尝试从 ServletContext 找 SpringWeb容器
@Nullable
protected WebApplicationContext findWebApplicationContext() {
//得到 contextAttribute 属性的值,作为查找依据 key (这个 contextAttribute 属性的值在何时被注入的? 看文章:SpringMVC源码:DispatcherServlet的初始化(一))
String attrName = getContextAttribute();
/*
若key为null,说明在 DispatcherServlet 中没有配置 contextAttribute 参数,则表明用户没打算从 ServletContext 中来决定SpringWeb容器。 直接返回null
*/
if (attrName == null) {
return null;
}
/*
若key不为null,说明在 DispatcherServlet 中配置了 contextAttribute 参数,则表明用户想从 ServletContext 中来决定SpringWeb容器。则根据此 key 去 ServletContext 中找,
找到,就用它来作为SpringWeb容器。没找到,则抛异常
*/
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
此方法的作用,适用于以下情况(了解即可):
比如,当前 ServletContext 中已经存在一个叫 “haha” 的 WebApplicationContext 了
//之前某些地方执行了以下代码:
servletContext.setAttribute("haha",new MyWebApplicationContext)
如果我们想使用这个 MyWebApplicationContext 来作为我们的SpringWeb容器,
那么就可以配置DispatcherServlet的 contextAttribute 属性值为 “haha” (一般我们不会在DispatcherServlet中配置contextAttribute属性,所以这种情况不常见)
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置了contextAttribute参数,值为haha-->
<init-param>
<param-name>contextAttribute</param-name>
<param-value>haha</param-value>
</init-param>
</servlet>
3、创建SpringWeb容器,并将rootContext设置为父容器
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// 获取 contextClass 属性的值,默认为XmlWebApplicationContext.class (contextClass属性有set方法,所以这里可以通过web.xml自定义contextClass的值)
Class<?> contextClass = getContextClass();
//...
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("..."); //若自定义了SpringWeb容器,那么此容器类型必须为ConfigurableWebApplicationContext类型,否则将抛异常
}
// 根据 contextClass 利用BeanUtils实例化SpringWeb容器对象,默认创建的SpringWeb容器为 XmlWebApplicationContext
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 设置SpringWeb容器的"父容器"(为rootContext)
wac.setParent(parent);
// 得到web.xml中DispatcherServlet配置的 configLocation 值,设置给SpringWeb容器的 configLocation 属性 (方便后续XmlWebApplicationContext容器刷新阶段,载入BeanDefinition)
// 注:如果没有配置,那么XmlWebApplicationContext默认会从"/WEB-INF/[Namespace]-servlet.xml"处加载,若Namespace属性也为空,则默认从"/WEB-INF/applicationContext.xml"处加载
//(看源码:XmlWebApplicationContext#loadBeanDefinitions方法中的getConfigLocations()和XmlWebApplicationContext#getDefaultConfigLocations())
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 配置和刷新SpringWeb容器(后面解析)
configureAndRefreshWebApplicationContext(wac);
return wac;
}
-
知识点1:可以看到,最终是根据 contextClass 属性的值,来确定SpringWeb容器类型的。默认创建的SpringWeb容器类型为XmlWebApplicationContext
这里显然也可以通过配置DispatcherServlet的 contextClass 属性值,来指定使用我们自定义的SpringWeb容器。如:(通常不会自定义,了解)
<servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name> contextClass </param-name> <param-value>com.itcode.springdemo.MyWebApplicationContext</param-value> </init-param> </servlet>
-
知识点2:配置和刷新容器
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { // 配置SpringWeb容器的id if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // 若在配置DispatcherServlet时,param name配置了"contextId"属性,那么就使用其值,作为容器id。 若没有配置,则默认生成id... if (this.contextId != null) { wac.setId(this.contextId); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName()); } } // 配置SpringWeb容器的 ServletContext 属性 wac.setServletContext(getServletContext()); // 配置SpringWeb容器的 ServletConfig 属性 wac.setServletConfig(getServletConfig()); // 配置SpringWeb容器的 Namespace 属性,默认为"[ServletName]-servlet" //(若在web.xml中没有配置configLocation,那么XmlWebApplicationContext加载BeanDefinition时,默认的configLocation就是 "/WEB-INF/[Namespace]-servlet.xml") //(此属性有set方法,所以可以通过web.xml配置DispatcherServlet时自定义) wac.setNamespace(getNamespace()); /* 给SpringWeb容器配置了一个监听器 SourceFilteringListener(待...) SourceFilteringListener可以根据输入的参数进行选择,所以实际监听的是ContextRefreshListener所监听的事件。 ContextRefreshListener是FrameworkServlet的内部类,监听ContextRefreshedEvent事件, 当接收到消息时调用FrameworkServlet的onApplicationEvent方法,在onApplicationEvent中会调用一次onRefresh方法,并将refreshEventReceived标志设置为true,表示已经onRefresh过 保证了 onRefresh 方法只会被执行一次 */ wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); //(待...) ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } // 留给子类实现(尚未有子类实现) postProcessWebApplicationContext(wac); //(待...) applyInitializers(wac); // 刷新SpringWeb容器 wac.refresh(); }
此部分总结:
此阶段完成了:
①、SpringWeb容器的创建,默认创建的SpringWeb容器为 XmlWebApplicationContext ,并为此容器设置了一些属性值
②、设置了父容器为rootContext
③、容器的刷新
4、调用 onRefresh 方法
刷新 SpringWeb 容器(这里的刷新,是向容器中设置了一些组件,并不是平常我们说的refresh)
此处逻辑交由 DispatcherServlet 完成(看下一篇)
【总结】
通过对 initWebApplicationContext 源码的分析,我们知道了:
0、SpringWeb容器的创建,发生在 DispatcherServlet 的初始化过程中。并且可以在web.xml中配置 DispatcherServlet 时,间接地配置 SpringWeb 容器(配置初始化参数< init-param>)
1、SpringWeb容器的来源有三处:
- 通过new FrameworkServlet()构造方法传入(在JavaConfig的配置情况下会用到)
- 在 ServletContext 中获取(不常用)
- 直接创建一个SpringWeb容器(一般都会走到这里)
2、在创建SpringWeb容器的过程中
- 默认创建的SpringWeb容器对象是 XmlWebApplicationContext。(也可以通过web.xml中DispatcherServlet配置 contextClass 属性,来指定自定义的SpringWeb容器(不常用))
- 为SpringWeb容器指定了父容器(父容器保存在ServletContext 中, key 为 org.springframework.web.context.WebApplicationContext.ROOT),如果没有在web.xml中配置监听器 ContextLoaderListener ,那么父容器就为null
- 完成了容器的刷新
3、可以总结下web.xml中关于DispatcherServlet 可配置的属性(理论上,在DispatcherServlet 中的属性,只要有对应的set方法,就都可以通过web.xml进行配置。 看笔记:SpringMVC源码:DispatcherServlet的初始化(一))
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 用于决定SpringWeb容器的-->
<!-- contextAttribute属性:尝试从ServletContext中的haha处决定SpringWeb容器-->
<init-param>
<param-name>contextAttribute</param-name>
<param-value>haha</param-value>
</init-param>
<!-- contextClass属性:自定义决定SpringWeb容器-->
<init-param>
<param-name>contextAttribute</param-name>
<param-value>com.itcode.springdemo.MyWebApplicationContext</param-value>
</init-param>
<!-- 给SpringWeb容器中的属性赋值的-->
<!-- contextConfigLocation属性:设置SpringWeb容器的BeanDefinition加载路径--> <!--只有此处是常用的-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<!-- contextId属性:设置SpringWeb容器的ID-->
<init-param>
<param-name>contextId</param-name>
<param-value>...</param-value>
</init-param>
<!-- namespace属性:设置SpringWeb容器的namespace-->
<init-param>
<param-name>namespace</param-name>
<param-value>...</param-value>
</init-param>
<!-- publishContext属性:用于决定是否将SpringWeb容器放到ServletContext域中-->
<init-param>
<param-name>publishContext</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
所以重点来说, FrameworkServlet 在初始化阶段做的事情就是:
根据web.xml中配置的 DispatcherServlet 信息,创建了一个SpringWeb容器(并为其设置了一些属性:如父容器、contextConfigLocation、容器ID…)。此时,还需要向容器中添加一些重要组件(如:处理器映射器、处理器适配器、视图解析器…),这部分的工作在 onRefresh 中,交给了子类 DispatcherServlet 完成(看下一篇)
参考书籍:《看透 springMVC 源代码分析与实践》