主题
之前Spring相关的一些类,比如Enviromnent,BenFactory都接触了一些.有一些收获.但是直接看代码很多方法都不知为什么这样写.哪里会用到.因为太底层了.不好理解..现在从高层次的调用开始再研究下.应该会有新的理解.
所以从一个Web应用初始化开始学习.看看它经历了哪些步骤.做了哪些事情.
之前对spring的dispatcherServlet有一点点研究(http://www.cnblogs.com/abcwt112/p/5283674.html).
ContextLoaderListener
1个最普通的WEB项目如果要在servlet环境中用Spring.肯定是在web.xml里配置1个listener.这个linstener是1个入口,在内部肯定会创建Spring相关的applicationcontext并配置它.
1 /* 2 * Copyright 2002-2015 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.web.context; 18 19 import javax.servlet.ServletContextEvent; 20 import javax.servlet.ServletContextListener; 21 22 /** 23 * Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}. 24 * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}. 25 * 26 *This listener should be registered after {
@link org.springframework.web.util.Log4jConfigListener} 27 * in {@code web.xml}, if the latter is used. 28 * 29 *As of Spring 3.1, {
@code ContextLoaderListener} supports injecting the root web 30 * application context via the {@link #ContextLoaderListener(WebApplicationContext)} 31 * constructor, allowing for programmatic configuration in Servlet 3.0+ environments. 32 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples. 33 * 34 * @author Juergen Hoeller 35 * @author Chris Beams 36 * @since 17.02.2003 37 * @see org.springframework.web.WebApplicationInitializer 38 * @see org.springframework.web.util.Log4jConfigListener 39 */ 40 public class ContextLoaderListener extends ContextLoader implements ServletContextListener { 41 42 /** 43 * Create a new {@code ContextLoaderListener} that will create a web application 44 * context based on the "contextClass" and "contextConfigLocation" servlet 45 * context-params. See {@link ContextLoader} superclass documentation for details on 46 * default values for each. 47 *This constructor is typically used when declaring {
@code ContextLoaderListener} 48 * as a {@code} within { @code web.xml}, where a no-arg constructor is 49 * required. 50 *The created application context will be registered into the ServletContext under
51 * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} 52 * and the Spring application context will be closed when the {@link #contextDestroyed} 53 * lifecycle method is invoked on this listener. 54 * @see ContextLoader 55 * @see #ContextLoaderListener(WebApplicationContext) 56 * @see #contextInitialized(ServletContextEvent) 57 * @see #contextDestroyed(ServletContextEvent) 58 */ 59 public ContextLoaderListener() { 60 } 61 62 /** 63 * Create a new {@code ContextLoaderListener} with the given application context. This 64 * constructor is useful in Servlet 3.0+ environments where instance-based 65 * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener} 66 * API. 67 *The context may or may not yet be {
@linkplain 68 * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it 69 * (a) is an implementation of {@link ConfigurableWebApplicationContext} and 70 * (b) has not already been refreshed (the recommended approach), 71 * then the following will occur: 72 *73 *
If the given context has not already been assigned an { @linkplain 74 * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it 75 *{ @code ServletContext} and {@code ServletConfig} objects will be delegated to 76 * the application context 77 *{ @link #customizeContext} will be called 78 *Any { @link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s 79 * specified through the "contextInitializerClasses" init-param will be applied. 80 *{ @link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called 81 * 82 * If the context has already been refreshed or does not implement 83 * {@code ConfigurableWebApplicationContext}, none of the above will occur under the 84 * assumption that the user has performed these actions (or not) per his or her 85 * specific needs. 86 *See {
@link org.springframework.web.WebApplicationInitializer} for usage examples. 87 *In any case, the given application context will be registered into the
88 * ServletContext under the attribute name {@link 89 * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring 90 * application context will be closed when the {@link #contextDestroyed} lifecycle 91 * method is invoked on this listener. 92 * @param context the application context to manage 93 * @see #contextInitialized(ServletContextEvent) 94 * @see #contextDestroyed(ServletContextEvent) 95 */ 96 public ContextLoaderListener(WebApplicationContext context) { 97 super(context); 98 } 99 100 101 /** 102 * Initialize the root web application context. 103 */ 104 @Override 105 public void contextInitialized(ServletContextEvent event) { 106 initWebApplicationContext(event.getServletContext()); 107 } 108 109 110 /** 111 * Close the root web application context. 112 */ 113 @Override 114 public void contextDestroyed(ServletContextEvent event) { 115 closeWebApplicationContext(event.getServletContext()); 116 ContextCleanupListener.cleanupAttributes(event.getServletContext()); 117 } 118 119 }
既然是1个listener.spring相关步骤肯定写在listener的contextInitialized方法里.内部很简单的调用了父类的initWebApplicationContext方法,并传入了servletContext对象作为参数.看方法名就知道这个方法肯定是要初始化WebApplicationContext.
1 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 2 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { 3 throw new IllegalStateException( 4 "Cannot initialize context because there is already a root application context present - " + 5 "check whether you have multiple ContextLoader* definitions in your web.xml!"); 6 } 7 8 Log logger = LogFactory.getLog(ContextLoader.class); 9 servletContext.log("Initializing Spring root WebApplicationContext"); 10 if (logger.isInfoEnabled()) { 11 logger.info("Root WebApplicationContext: initialization started"); 12 } 13 long startTime = System.currentTimeMillis(); 14 15 try { 16 // Store context in local instance variable, to guarantee that 17 // it is available on ServletContext shutdown. 18 if (this.context == null) { 19 this.context = createWebApplicationContext(servletContext); 20 } 21 if (this.context instanceof ConfigurableWebApplicationContext) { 22 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; 23 if (!cwac.isActive()) { 24 // The context has not yet been refreshed -> provide services such as 25 // setting the parent context, setting the application context id, etc 26 if (cwac.getParent() == null) { 27 // The context instance was injected without an explicit parent -> 28 // determine parent for root web application context, if any. 29 ApplicationContext parent = loadParentContext(servletContext); 30 cwac.setParent(parent); 31 } 32 configureAndRefreshWebApplicationContext(cwac, servletContext); 33 } 34 } 35 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 36 37 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 38 if (ccl == ContextLoader.class.getClassLoader()) { 39 currentContext = this.context; 40 } 41 else if (ccl != null) { 42 currentContextPerThread.put(ccl, this.context); 43 } 44 45 if (logger.isDebugEnabled()) { 46 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + 47 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); 48 } 49 if (logger.isInfoEnabled()) { 50 long elapsedTime = System.currentTimeMillis() - startTime; 51 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); 52 } 53 54 return this.context; 55 } 56 catch (RuntimeException ex) { 57 logger.error("Context initialization failed", ex); 58 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); 59 throw ex; 60 } 61 catch (Error err) { 62 logger.error("Context initialization failed", err); 63 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); 64 throw err; 65 } 66 }
几个比较重要的步骤是:
1. 19行this.context = createWebApplicationContext(servletContext);
初始化1个WebApplicationContext,默认是XmlWebApplicationContext通过BeanUtils.instantiateClass创建的,XmlWebApplicationContext这个类名是写在org\springframework\web\context\ContextLoader.properties里的.
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
不喜欢的话自己也可以通过servletContext里去配置contextClass这个参数指定class来配置WebApplicationContext.但是我想一般没人这么做吧.
2. 32行configureAndRefreshWebApplicationContext(cwac, servletContext);
初始化wac.大概是最重要的入口了.后面再仔细看.
3. 35行servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
如果前面初始化成功的话就把wac绑定到servletContext的attr中(org.springframework.web.context.WebApplicationContext.ROOT),.所以其实我们在servlet环境中如果要获取wac,要么通过ApplicationContextAware要么通过这个servletContext的attr...都是可以的.
configureAndRefreshWebApplicationContext方法
1 protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { 2 if (ObjectUtils.identityToString(wac).equals(wac.getId())) { 3 // The application context id is still set to its original default value 4 // -> assign a more useful id based on available information 5 String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); 6 if (idParam != null) { 7 wac.setId(idParam); 8 } else { 9 // Generate default id... 10 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + 11 ObjectUtils.getDisplayString(sc.getContextPath())); 12 } 13 } 14 15 wac.setServletContext(sc); 16 String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); 17 if (configLocationParam != null) { 18 wac.setConfigLocation(configLocationParam); 19 } 20 21 // The wac environment's #initPropertySources will be called in any case when the context 22 // is refreshed; do it eagerly here to ensure servlet property sources are in place for 23 // use in any post-processing or initialization that occurs below prior to #refresh 24 ConfigurableEnvironment env = wac.getEnvironment(); 25 if (env instanceof ConfigurableWebEnvironment) { 26 // 一开始env里的propertySources里面的servletContextInitParams和servletContextConfigParams都是空的,需要用相应的servlet的值去替换 27 ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); 28 } 29 30 customizeContext(sc, wac); 31 wac.refresh(); 32 }
然后我们来看看这个最重要的初始化wac的方法.
L15 wac.setServletContext(sc); wac要初始化需要servlet环境相关的数据.
L16.获取servletContext里配置的contextConfigLocation的值.这个变量大家肯定不陌生.这个就是需要加载的spring配置文件的路径地址.
L18 contextConfigLocation值设置给wac.所以wac初始化需要spring的配置文件(废话).
L24-28 让wac的env去加载servlet环境相关的数据.因为之前wac是用beanUtils创建的.创建的时候并不知道你当前的环境有什么变量.所以这里需要加载一下servlet环境的properties.
initPropertySources有2个参数.第一个servletContext.第二个是servletConfig.很明显.这里是在listener而不是springMVC的dispatcherServlet里,所以这里servletConfig是null.
L30 customizeContext(sc, wac);在wac启动之前允许你定制一下.
1 /** 2 * Customize the {@link ConfigurableWebApplicationContext} created by this 3 * ContextLoader after config locations have been supplied to the context 4 * but before the context is refreshed. 5 *The default implementation {
@linkplain #determineContextInitializerClasses(ServletContext) 6 * determines} what (if any) context initializer classes have been specified through 7 * {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and 8 * {@linkplain ApplicationContextInitializer#initialize invokes each} with the 9 * given web application context. 10 *Any {
@code ApplicationContextInitializers} implementing 11 * {@link org.springframework.core.Ordered Ordered} or marked with @{@link 12 * org.springframework.core.annotation.Order Order} will be sorted appropriately. 13 * 14 * @param sc the current servlet context 15 * @param wac the newly created application context 16 * @see #CONTEXT_INITIALIZER_CLASSES_PARAM 17 * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext) 18 */ 19 protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) { 20 // 找到 web.xml里配置的 globalInitializerClasses 和 contextInitializerClasses 对应的class 21 List>> initializerClasses = 22 determineContextInitializerClasses(sc); 23 // 一般情况下没人配置,所以是empty. 24 if (initializerClasses.isEmpty()) { 25 // no ApplicationContextInitializers have been declared -> nothing to do 26 return; 27 } 28 29 // 如果这些ApplicationContextInitializer存在的话就调用他们的initialize方法. 30 ArrayList > initializerInstances = 31 new ArrayList >(); 32 33 for (Class > initializerClass : initializerClasses) { 34 Class> initializerContextClass = 35 GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); 36 if (initializerContextClass != null) { 37 Assert.isAssignable(initializerContextClass, wac.getClass(), String.format( 38 "Could not add context initializer [%s] since its generic parameter [%s] " + 39 "is not assignable from the type of application context used by this " + 40 "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(), 41 wac.getClass().getName())); 42 } 43 initializerInstances.add(BeanUtils.instantiateClass(initializerClass)); 44 } 45 46 AnnotationAwareOrderComparator.sort(initializerInstances); 47 for (ApplicationContextInitializer initializer : initializerInstances) { 48 initializer.initialize(wac); 49 } 50 }
可以参考我写的注释.就是允许你在servletContext中配置2个参数 globalInitializerClasses 和 contextInitializerClasses.他们对应的类都是ApplicationContextInitializer的实现.如果你配置了.那这里会调用
initializer.initialize(wac);
这个方法执行你的回调函数.
我记得我第一家公司这里好像加载了一些properties文件..(虽然我现在想想不明白为什么要在这里加载而不是直接配置在spring的配置文件中....或者直接使用@PropertySource或者@ConfigurationProperties)..算是1个spring的扩展点吧.
L31 前面的配置也做完了..那就真正开始初始化wac了.执行wac.refresh();刷新wac.
小结
主要学习了XmlWebApplicationContext刷新前的ContextLoaderListener做的一些预备工作..比如:
1.默认加载的是哪个wac.你也可以自己定制.
2.env读取servletContext与Config的参数
3.自定义的customer,ApplicationContextInitializer
4.初始化wac成功以后绑定到servletContext的attr中
等等...
后续准备研究wac是怎么refresh的.