本文分析了Spring Web 项目的启动过程,从 /webapps/web.xml 为入口。
目录
Web项目中的启动配置文件 web.xml
web.xml 文件是用来初始化工程配置信息的。比如说welcome页面,filter,listener,servlet, servlet-mapping,启动加载级别等等。我们可以关注到在web.xml 文件中有以下两个标签的配置。
// servlet容器启动监听器// 初始化参数 - 用于容器启动所需要的读取的配置的文件位置 org.springframework.web.context.ContextLoaderListener 复制代码 contextConfigLocation classpath:applicationContext.xml
容器启动的监听器 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 容器@Nullableprivate 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);复制代码