本文分析了Spring Web 项目的启动过程,从 /webapps/web.xml 为入口。
目录
Web项目中的启动配置文件 web.xml
web.xml 文件是用来初始化工程配置信息的。比如说welcome页面,filter,listener,servlet, servlet-mapping,启动加载级别等等。我们可以关注到在web.xml 文件中有以下两个标签的配置。
// servlet容器启动监听器
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
// 初始化参数 - 用于容器启动所需要的读取的配置的文件位置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
复制代码
容器启动的监听器 ContextLoaderListener
spring的启动是依赖于
ContextLoaderListener#contextInitialized
的父类方法ContextLoader#initWebApplicationContext
进行。
/**
* ServletContext 监听器
*/
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
// ServletContext初始化之前执行
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
// ServletContext初始化之前执行
@Override
public void contextDestroyed(ServletContextEvent event) {
// 关闭WEB容器
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
public class ContextLoader {
// Web 容器
@Nullable
private WebApplicationContext context;
// 省略其他代码
...
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 如果容器已经存在,抛出异常 避免重复创建root上下文,保证只有一个Spring容器
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!");
}
try {
// 如果当前容器为空则创建一个 WebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
// 如果容器已经存在,并且是一个可配置的WebApplicationContext
// ConfigurableWebApplicationContext是个接口,而且顾名思义:可配置的WebApplicationContext
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
// 容器是否已经被激活 -- prepareRefresh(){this.active.set(true); ....}
if (!cwac.isActive()) {
// 设置父级上下文 上下文ID 环境
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置和刷新 WebApplContext容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将配置并且刷新过的容器存入servlet上下文中,并以WebApplicationContext的类名作为key值
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) {
...
}
}
}
复制代码
如何创建 WebApplicationContext
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 获取用户配置的容器 获取获取系统默认容器实现类
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// 实例化并强转
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
// 查询用户是否自定义了 context
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
// 通过contextClassName 和 反射获取类
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
// 从ContextLoader.properties获取默认配置 默认容器是XmlWebApplicationContext
else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
复制代码
如何配置并刷新容器 configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// Unique id for this context 唯一的ID
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// 生成默认ID
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 将servlet上下文 注入 webApplicationContext
wac.setServletContext(sc);
// 获取在web.xml 中配置的application.xml 路径
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// 获取环境
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 主要用于自定义context相关配置,比如定义bean是否可以循环引用,bean定义是否可以被覆盖等,通常情况下不做任何操作。
customizeContext(sc, wac);
// 刷新 WebApplicationContext 容器
wac.refresh();
}
复制代码
将容器初始化到 Servlet
上下文
servletContext.
setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
复制代码