使用SpringMVC时,需要不论是使用注解配置,还是使用XML配置Bean,他们都会在Web服务器启动后就初始化。根据J2ee的知识可以知道,肯定是使用了ServletContextListener才完成的这个功能。那Spring又是如何实现的呢?还有我们在Web.xml配置的那些applicationContext.xml相关的XML文件的位置(配置方式多样),又是如何读取到相应的文件的呢,读取到这些文件后,是如何初始化类的呢?我们能不能自定义初始化过程或者自定义WebApplicationContext呢?WebApplicationContext消亡时又做了哪些事情呢?带着这些疑问,就来查阅一下Spring的源码。
找ServletContextListener
在使用SpringMVC时,都会配置一个监听器:
org.springframework.web.context.ContextLoaderListener,这个监听器应该就是我们要找的那个ServletContextListener吧?
1 /* 2 Bootstrap listener to start up and shut down Spring‘s root WebApplicationContext. Simply delegates to ContextLoader as well as to ContextCleanupListener. 3 4 This listener should be registered after org.springframework.web.util.Log4jConfigListener in web.xml, if the latter is used. 5 6 */ 7 8 public class ContextLoaderListener extends ContextLoader implements ServletContextListener
根据ContextLoaderListener的定义和说明来看,确实是我们要找的。因为说明中提到:这个监听器用于启动和关闭Spring的WebApplicationContext
(也就是WebApplicationContextd 初始化和消亡)
。但是启动和关闭的动作并不是由这个监听器来实现的它只是一个委托。
另外还提到:如果在web.xml要使用Log4jConfigListener
,那么应该将Log4jConfigListener放在
ContextLoaderListener的前面。
根据这个类的说明,可以知道这个监听器就是我们要找的那个ServletContextListener。那么根据它顺藤摸瓜,应该就能知道如何构造WebApplicationContext的)。
接下来看看contextInitialized:
public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader(); if (this.contextLoader == null) { this.contextLoader = this; } this.contextLoader.initWebApplicationContext(event.getServletContext()); }
初始化WebApplicationContext的工作是在initWebApplicationContext完成的。
如何初始化WebApplicationContext
可以在web.xml设置的相关配置
在了解initWebApplicationContext中做了什么事之前,先来了解一下ContextLoader可以做什么:
·这个类用于初始化WebApplicationContext,并且是在ContextLoaderListener中调用初始化。
/** * Performs the actual initialization work for the root application context. * Called by {@link ContextLoaderListener}. * * <p>Looks for a {@link #CONTEXT_CLASS_PARAM "contextClass"} parameter * at the <code>web.xml</code> context-param level to specify the context * class type, falling back to the default of * {@link org.springframework.web.context.support.XmlWebApplicationContext} * if not found. With the default ContextLoader implementation, any context class * specified needs to implement the ConfigurableWebApplicationContext interface. * * <p>Processes a {@link #CONFIG_LOCATION_PARAM "contextConfigLocation"} * context-param and passes its value to the context instance, parsing it into * potentially multiple file paths which can be separated by any number of * commas and spaces, e.g. "WEB-INF/applicationContext1.xml, * WEB-INF/applicationContext2.xml". Ant-style path patterns are supported as well, * e.g. "WEB-INF/*Context.xml,WEB-INF/spring*.xml" or "WEB-INF/**/*Context.xml". * If not explicitly specified, the context implementation is supposed to use a * default location (with XmlWebApplicationContext: "/WEB-INF/applicationContext.xml"). * * <p>Note: In case of multiple config locations, later bean definitions will * override ones defined in previously loaded files, at least when using one of * Spring‘s default ApplicationContext implementations. This can be leveraged * to deliberately override certain bean definitions via an extra XML file. * * <p>Above and beyond loading the root application context, this class * can optionally load or obtain and hook up a shared parent context to * the root application context. See the * {@link #loadParentContext(ServletContext)} method for more information. */
从类的说明中,可以了解到,会涉及到下列的context-param配置:
1、contextClass:
初始化时,会先在web.xml中<context-param>中查找有没有一个叫做contextClass的配置。这个配置是用于自定义WebApplicationContext的实现类(自定义的WebApplicationContext的实现类,必须实现ConfigurableWebApplicationContext接口,这是为什么呢?答案将在下文中找到)。如果没有指定这个上下文参数,就默认使用XmlWebApplicationContext。
2、contextConfigLocation
这个参数用于applicationContext*.xml文件的位置。
在这个配置中,可以设置多个文件:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/applicationContext1.xml,WEB-INF/applicationContext2.xml</param-value> </context-param>
多个配置文件之间可以用逗号或者空格区分。
也可以用WEB-INF/*Context.xml,WEB-INF/spring*.xml这样的通配符方式设置。
如果不在context-param中设定contextConfigLocation:就使用默认值:/WEB-INF/applicationContext.xml
另外:在实际的项目中,如果指定了多个applicationContext.xml文件,在这么多个文件中,如有<bean>重名,就会自动的让后面的覆盖前面的。
此外,这两个参数名是固定的,因为:
public static final String CONTEXT_CLASS_PARAM = "contextClass"; public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
webApplicationContext初始化过程
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { // 先判断是否已经初始化过了 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!"); } Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started"); } long startTime = System.currentTimeMillis(); try { // 看看有没有父上下文,如果有就会将父上下问的信息合并到本上下文中 // Determine parent for root web application context, if any. ApplicationContext parent = loadParentContext(servletContext); // Store context in local instance variable, to guarantee that // it is available on ServletContext shutdown. 这行代码就是包含Spring加载并初始化bean的调用: this.context = createWebApplicationContext(servletContext, parent); 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); } if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } return this.context; } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Context initialization failed", err); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; } }
接下来看看如何创建WebApplicationContext实例的:
protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) { // 判断自定义的WebContextContext类是否实现了ConfigurableWebApplicationContext接口: Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } // 利用反射创建WebApplicationContext实现类对象 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); // Assign the best possible id value. if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) { // Servlet <= 2.4: resort to name specified in web.xml, if any. String servletContextName = sc.getServletContextName(); wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(servletContextName)); } else { // Servlet 2.5‘s getContextPath available! try { // 通过反射调用getContextPath方法。 // 怎么老感觉Java中的反射调用方法与JavaScript中的YourFunction.call(caller,params)是那么的相像呢,不过他们的本质是不一样的。 String contextPath = (String) ServletContext.class.getMethod("getContextPath").invoke(sc); wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(contextPath)); } catch (Exception ex) { throw new IllegalStateException("Failed to invoke Servlet 2.5 getContextPath method", ex); } } wac.setParent(parent); wac.setServletContext(sc); wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM)); // 自定义的ContextLoader可以重写这个方法,以添加一些赋值工作。 // 使用了Template方法模式 customizeContext(sc, wac); // 这里是真正的初始化配置的bean的操作。 wac.refresh(); return wac; }
这样整个WebApplicationContext就创建完毕了。
如何指定默认的WebApplicationContext的呢
在上面的创建上下文的过程中,没有指定默认的WebApplicationContext呀,它是怎么办到呢?
查看类中的代码发现一个静态代码块:
static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load ‘ContextLoader.properties‘: " + ex.getMessage()); } }
这里就是在初始化ContextLoader的实例,也就是ContextLoaderListener的实例时,就会先制定一个默认的WebApplicationContext 了。它使用了一个properties文件:ContextLoader.properties,文件的位置:
文件中只有一条配置:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
也就是说默认使用了XmlWebApplicationContext的。
ContextLoader提供的辅助功能:
public static WebApplicationContext getCurrentWebApplicationContext() { ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl != null) { WebApplicationContext ccpt = currentContextPerThread.get(ccl); if (ccpt != null) { return ccpt; } } return currentContext; }
这样一来,我们就可以在程序中直接获取WebApplicationContext对象了。
初始化beans
经过上面的查阅,知道默认是使用XmlWebApplicationContext作为上下文的,那么这个类中肯定包含了初始化beans的过程。上面的分析知道,使用了refresh()方法初始化的。
再查看refresh()方法前,先来看看对于XmlWebApplicationContext的说明,这是很有必要的,说明中提到了:
/** * {@link org.springframework.web.context.WebApplicationContext} implementation * which takes its configuration from XML documents, understood by an * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}. * This is essentially the equivalent of * {@link org.springframework.context.support.AbstractXmlApplicationContext} * for a web environment. * * <p>By default, the configuration will be taken from "/WEB-INF/applicationContext.xml" * for the root context, and "/WEB-INF/test-servlet.xml" for a context with the namespace * "test-servlet" (like for a DispatcherServlet instance with the servlet-name "test"). * * <p>The config location defaults can be overridden via the "contextConfigLocation" * context-param of {@link org.springframework.web.context.ContextLoader} and servlet * init-param of {@link org.springframework.web.servlet.FrameworkServlet}. Config locations * can either denote concrete files like "/WEB-INF/context.xml" or Ant-style patterns * like "/WEB-INF/*-context.xml" (see {@link org.springframework.util.PathMatcher} * javadoc for pattern details). * * <p>Note: In case of multiple config locations, later bean definitions will * override ones defined in earlier loaded files. This can be leveraged to * deliberately override certain bean definitions via an extra XML file. * * <p><b>For a WebApplicationContext that reads in a different bean definition format, * create an analogous subclass of {@link AbstractRefreshableWebApplicationContext}.</b> * Such a context implementation can be specified as "contextClass" context-param * for ContextLoader or "contextClass" init-param for FrameworkServlet. */
1、WebApplicationContext的实现类将根据XmlBeanDefinitionReader获取到bean的配置信息。
2、bean配置的来源有两个, 这两个位置配置的Bean都将被XmlBeanDefinitionReader理解并使用BeanFactory创建:
1)上面说的contextConfigLocation,
2)DispatcherServlet中配置的/WEB-INF/*-servlet.xml
web.xml中为DispatcherServler中设置的配置文件位置的方式是:
<servlet>
<servlet-name>test</servlet>
<servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/*-servlet.xml</param-value>
</init-param>
</servlet>
这里的配置方式与<context-param>中的配置方式类似。
如果不指定,默认的上就是/WEB-INF/*-servlet.xml 。例如上面的使用test作为servlet-name,也就是说namespace是test,如果不配置,默认设置就是/WEB-INF/test-servlet.xml
根据XmlWebApplicationContext的与配置相关的类图:
从上面可以看出,XmlWebApplication,实现了ConfigurableWebApplicationContext接口,这个接口中提供了关于ServletConfig,ServletContext,ConfigLocations,Namespace的操作,并且继承了WebApplicationContext ,而这些操作都是必要的,所以自定义的WebApplicationContext,也得实现ConfigurableWebApplicationContext接口才行。
refresh:
在AbstractApplicationContext类中,实现了refresh:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset ‘active‘ flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
这个过程要处理的事情很多,不是现在就能看懂的,所以先把这个问题放一边去,以后再看。
Spring源码阅读:Spring WebApplicationContext初始化与消亡,布布扣,bubuko.com