Spring 学习记录7 初识XmlWebApplicationContext

主题

之前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 }

View Code

既然是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 }

View Code

可以参考我写的注释.就是允许你在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的.

 

你可能感兴趣的:(Spring 学习记录7 初识XmlWebApplicationContext)