这一节主要是记录一下Spring Ioc 容器的启动过程。
Spring 的 Ioc 容器是怎么被加载和使用的? web容器为它提供了宿主环境 ServlectContext, Tomcat 启动时会读取web.xml。
并且实例化web.xml中配置的ContextLoaderListener ,下面看一下ContextLoaderListener的创建过程:
在实例化ContextLoaderListener 之后,通过接口回调执行ContextLoaderListener 类中的contextInitialized(ServletContextEvent event)方法,该方法的作用是初始化根上下文
(注:WebApplicationContext 是ApplicationContext 的拓展接口,而Spring默认提供的根上下文是XmlWebApplicationContext。)
代码:
public WebApplicationContext initWebApplicationContext(ServletContext 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!"); } //…………省略 //初始化 WebApplicationContext servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started"); } long startTime = System.currentTimeMillis(); try { // Store context in local instance variable, to guarantee that // it is available on ServletContext shutdown. if (this.context == null) { //创建webApplication的实例 this.context =createWebApplicationContext(servletContext); } //省略代码在下面给出 }
createWebApplicationContext(servletContext) 方法的作用是创建根上下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); }
这里determineContextClass(ServletContext sc) 用于返回ApplicationContext的实现类,如果没有在配置文件中配置自定义的ApplicationContext,默认返回 XmlWebApplicationContext ,得到实例后对其进行检查,满足条件则返回它的实例。
createWebApplicationContext方法下面省略的代码:
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); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
这一步是判断返回的根上下文实例context有没有父上下文,取决于在web.xml中定义的参数:locatorFactorySelector,这是一个可选参数,加载完父上下文之后,执行configureAndRefreshWebApplicationContext方法,先看一下源码:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { 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 //………省略 } wac.setServletContext(sc); 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 conte //xt // 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 //………省略 wac.refresh(); }
上面的方法setServletContext 为根上下文设置ServletContext的引用 , 执行根上下文的refresh()方法, 就是上一节说的,创建BeanFacotry的过程,执行完这方法在启动日志就可以看到
信息: Loading XML bean definitions from class path resource [spring.xml]
这个时候就完成了对spring.xml资源的读取,到此就结束了根上下文(Application)的配置与加载资源,servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context)这句代码就是把上下文存入ServletContext当中,下次获取根上下文的时候就可以通过 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性获取。
到此ContextLoaderListener的创建就结束了。