ContextLoaderListener到refresh之间的过程
web.xml 中spring的配置:
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationcontext*.xml</param-value> </context-param>
所以这个入口是ContextLoaderListener对象调用的#contextInitialized 方法:
@Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
由于继承关系,所以要先初始化ContextLoader
ContextLoader中有这样一段静态代码块:
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
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()); } }
defaultStrategies这个配置文件是:
回到ContextLoaderListener:
@Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
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!"); } servletContext.log("Initializing Spring root WebApplicationContext"); Log logger = LogFactory.getLog(ContextLoader.class); 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) {
// 1. 这里就是获得XmlWebApplicationContext实例 this.context = createWebApplicationContext(servletContext); } 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); }
// 2. 这里是配置和刷新webApplicationContext configureAndRefreshWebApplicationContext(cwac, servletContext); } } 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.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms"); } return this.context; } catch (RuntimeException | Error ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } }
进1:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = this.determineContextClass(sc); // 进这里 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } else { return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); } }
由于没配contextClass, 所以拿到的contextClassName是null, 也就是拿defaultStrategies的配置,也就是上面的XmlWebApplicationContext值, 然后Class.forName拿到XmlWebApplicationContext.
BeanUtils.instantiateClass, 就是创建XmlWebApplicationContext实例
所以进1返回的是XmlWebApplicationContext实例
进2 看configureAndRefreshWebApplicationContext方法:
要弄清楚这个方法的入参cwac, 其实是一个XmlWebApplicationContext , 因为 this.context在上面已经看到了,是XmlWebApplicationContext, 只是将它强转成了ConfigurableWebApplicationContext
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 String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } wac.setServletContext(sc);
// 3. 获取web.xml中 contextConfigLocation的值 /WEB-INF/applicationcontext*.xml
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
// 4 设置wac的ConfigLocaion 为:/WEB-INF/applicationcontext*.xml wac.setConfigLocation(configLocationParam); } // The wac environment's #initPropertySources will be called in any case when the context // 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
// 5 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) {
// 6 替换env中的源数据为ServletContext的源数据 ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } // 7. 自定义 customizeContext(sc, wac);
// 8. spring的主入口 wac.refresh(); }
进3, 4 :
就是解析web.xml中的contextConfigLocation, 然后设置给was
这一步我源码有点问题没解决,就不进去看了。
进5:
这里我绕了很久一开始没看懂。要注意继承关系:
先看看XmlWebApplicationContext的继承关系 ,关注这几个:
wac.getEnvironment() 虽然形参是ConfigurableWebApplicationContext, 但是实际上它是XmlWebApplicationContext(前面特意解释过)
子类没有的方法去父类中找,所以找到了AbstractApplicationContext的getEnvironment,
在里面调用createEnvironment 方法的时候,又是对XmlWebApplicationContext对象 调用 createEnvironment方法,子类中没有的往上找,所以找到的是AbstractRefreshWebApplicationContext的createEnvironment方法:
看看这个new StandardServletEnvironment , 先看StandardServletEnvironment 继承关系
所以看看AbstractEnvironment的构造方法:
public AbstractEnvironment() { this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources); this.customizePropertySources(this.propertySources); }
这里的this是StandardServletEnvironment对象, 所以调用StandardServletEnvironment的customizePropertySources方法:
这里两个addLast是添加属性源作为占位符
进6 就不讲了,实际就是从ServletContext拿到source, 给env塞两个实际的source进去
7 自定义上下文:
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) { List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc); for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) { Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) { throw new ApplicationContextException(String.format( "Could not apply context initializer [%s] since its generic parameter [%s] " + "is not assignable from the type of application context used by this " + "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(), wac.getClass().getName())); } this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass)); } AnnotationAwareOrderComparator.sort(this.contextInitializers); for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { initializer.initialize(wac); } }
这里就两部,拿到所有的ApplicationContextInitializer ,然后调用initialize方法
从web.xml中拿到param-name : contextInitializerClasses 对应的value,
所以这里可以自定义扩展, 在was.refresh之前做一些对XmlWebApplicationContext做一些自定义操作
以下是代码示例:
web.xml中添加:
<context-param> <param-name>contextInitializerClasses</param-name> <param-value>com.readspring.MyApplicationContextInitializer</param-value> </context-param>
自定义类实现ApplicationContextInitializer接口,这里就只打印一句话示例:
public class MyApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("MyApplicationContextInitializer"); } }
结果:
进8 was.refresh就是ioc的主入口了, 完。