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  * <p>This listener should be registered after {@link org.springframework.web.util.Log4jConfigListener}
 27  * in {@code web.xml}, if the latter is used.
 28  *
 29  * <p>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      * <p>This constructor is typically used when declaring {@code ContextLoaderListener}
 48      * as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
 49      * required.
 50      * <p>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      * <p>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 <strong>not</strong> already been refreshed (the recommended approach),
 71      * then the following will occur:
 72      * <ul>
 73      * <li>If the given context has not already been assigned an {@linkplain
 74      * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
 75      * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
 76      * the application context</li>
 77      * <li>{@link #customizeContext} will be called</li>
 78      * <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s
 79      * specified through the "contextInitializerClasses" init-param will be applied.</li>
 80      * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
 81      * </ul>
 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      * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
 87      * <p>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 <em>refreshed</em>.
 5      * <p>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      * <p>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<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> 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<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances =
31                 new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
32 
33         for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> 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<ConfigurableApplicationContext> 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的.

 

posted @ 2017-10-30 10:40  abcwt112  阅读(4270)  评论(0编辑  收藏  举报