前言: 这是关于Spring的第三篇文章, 打算后续还会写入AOP 和Spring 事务管理相关的文章, 这么好的两个周末 都在看code了, 确实是有所收获, 现在就来记录一下. 在上一篇讲解Spring IOC的文章中, 每次产生ApplicationContext工厂的方式是: ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
这样产生applicationContext 就有一个弊端, 每次访问加载bean 的时候都会产生这个工厂, 所以 这里需要解决这个问题. 解决问题的方法很简单, 在web 启动的时候将applicationContext转到到servletContext中, 因为在web 应用中的所有servlet都共享一个servletContext对象. 那么我们就可以利用ServletContextListener去监听servletContext事件, 当web 应用启动的是时候, 我们就将applicationContext 装载到servletContext中. 然而Spring容器底层已经为我们想到了这一点, 在spring-web-xxx-release.jar包中有一个 已经实现了ServletContextListener的类, 下面我们就来看一下这个类:
1 public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
2
3 private ContextLoader contextLoader;
4
5
6 /**
7 * Create a new {@code ContextLoaderListener} that will create a web application
8 * context based on the "contextClass" and "contextConfigLocation" servlet
9 * context-params. See {@link ContextLoader} superclass documentation for details on
10 * default values for each.
11 * <p>This constructor is typically used when declaring {@code ContextLoaderListener}
12 * as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
13 * required.
14 * <p>The created application context will be registered into the ServletContext under
15 * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
16 * and the Spring application context will be closed when the {@link #contextDestroyed}
17 * lifecycle method is invoked on this listener.
18 * @see ContextLoader
19 * @see #ContextLoaderListener(WebApplicationContext)
20 * @see #contextInitialized(ServletContextEvent)
21 * @see #contextDestroyed(ServletContextEvent)
22 */
23 public ContextLoaderListener() {
24 }
25
26 /**
27 * Create a new {@code ContextLoaderListener} with the given application context. This
28 * constructor is useful in Servlet 3.0+ environments where instance-based
29 * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
30 * API.
31 * <p>The context may or may not yet be {@linkplain
32 * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
33 * (a) is an implementation of {@link ConfigurableWebApplicationContext} and
34 * (b) has <strong>not</strong> already been refreshed (the recommended approach),
35 * then the following will occur:
36 * <ul>
37 * <li>If the given context has not already been assigned an {@linkplain
38 * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
39 * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
40 * the application context</li>
41 * <li>{@link #customizeContext} will be called</li>
42 * <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s
43 * specified through the "contextInitializerClasses" init-param will be applied.</li>
44 * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
45 * </ul>
46 * If the context has already been refreshed or does not implement
47 * {@code ConfigurableWebApplicationContext}, none of the above will occur under the
48 * assumption that the user has performed these actions (or not) per his or her
49 * specific needs.
50 * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
51 * <p>In any case, the given application context will be registered into the
52 * ServletContext under the attribute name {@link
53 * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
54 * application context will be closed when the {@link #contextDestroyed} lifecycle
55 * method is invoked on this listener.
56 * @param context the application context to manage
57 * @see #contextInitialized(ServletContextEvent)
58 * @see #contextDestroyed(ServletContextEvent)
59 */
60 public ContextLoaderListener(WebApplicationContext context) {
61 super(context);
62 }
63
64 /**
65 * Initialize the root web application context.
66 */
67 public void contextInitialized(ServletContextEvent event) {
68 this.contextLoader = createContextLoader();
69 if (this.contextLoader == null) {
70 this.contextLoader = this;
71 }
72 this.contextLoader.initWebApplicationContext(event.getServletContext());
73 }
74
75 /**
76 * Create the ContextLoader to use. Can be overridden in subclasses.
77 * @return the new ContextLoader
78 * @deprecated in favor of simply subclassing ContextLoaderListener itself
79 * (which extends ContextLoader, as of Spring 3.0)
80 */
81 @Deprecated
82 protected ContextLoader createContextLoader() {
83 return null;
84 }
85
86 /**
87 * Return the ContextLoader used by this listener.
88 * @return the current ContextLoader
89 * @deprecated in favor of simply subclassing ContextLoaderListener itself
90 * (which extends ContextLoader, as of Spring 3.0)
91 */
92 @Deprecated
93 public ContextLoader getContextLoader() {
94 return this.contextLoader;
95 }
96
97
98 /**
99 * Close the root web application context.
100 */
101 public void contextDestroyed(ServletContextEvent event) {
102 if (this.contextLoader != null) {
103 this.contextLoader.closeWebApplicationContext(event.getServletContext());
104 }
105 ContextCleanupListener.cleanupAttributes(event.getServletContext());
106 }
107
108 }
这里就监听到了servletContext的创建过程, 那么 这个类又是如何将applicationContext装入到serveletContext容器中的呢? 我们接着来看看 :this.contextLoader.initWebApplicationContext(event.getServletContext()) 方法:
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 }
这里的重点是:servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 用key:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE value: this.context的形式将applicationContext装载到servletContext中了. 另外从上面的一些注释我们可以看出: WEB-INF/applicationContext.xml, 如果我们项目中的配置文件不是这么一个路径的话 那么我们使用ContextLoaderListener 就会 出问题, 所以我们还需要在web.xml中配置我们的applicationContext.xml配置文件的路径.
1 <listener>
2 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
3 </listener>
4
5 <context-param>
6 <param-name>contextConfigLocation</param-name>
7 <param-value>classpath:applicationContext.xml</param-value>
8 </context-param>
剩下的就是在项目中开始使用 servletContext中装载的applicationContext对象了: 那么这里又有一个问题, 装载时的key 是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 我们在代码中真的要使用这个吗? 其实Spring为我们提供了一个工具类WebApplicationContextUtils, 接着我们先看下如何使用, 然后再去看下这个工具类的源码:
WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
接着来看下这个工具了的源码:
1 /**
2 * Find the root WebApplicationContext for this web application, which is
3 * typically loaded via {@link org.springframework.web.context.ContextLoaderListener}.
4 * <p>Will rethrow an exception that happened on root context startup,
5 * to differentiate between a failed context startup and no context at all.
6 * @param sc ServletContext to find the web application context for
7 * @return the root WebApplicationContext for this web app, or {@code null} if none
8 * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
9 */
10 public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
11 return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
12 }
1 /**
2 * Find a custom WebApplicationContext for this web application.
3 * @param sc ServletContext to find the web application context for
4 * @param attrName the name of the ServletContext attribute to look for
5 * @return the desired WebApplicationContext for this web app, or {@code null} if none
6 */
7 public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
8 Assert.notNull(sc, "ServletContext must not be null");
9 Object attr = sc.getAttribute(attrName);
10 if (attr == null) {
11 return null;
12 }
13 if (attr instanceof RuntimeException) {
14 throw (RuntimeException) attr;
15 }
16 if (attr instanceof Error) {
17 throw (Error) attr;
18 }
19 if (attr instanceof Exception) {
20 throw new IllegalStateException((Exception) attr);
21 }
22 if (!(attr instanceof WebApplicationContext)) {
23 throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
24 }
25 return (WebApplicationContext) attr;
26 }
这里就能很直观清晰地看到 通过key值直接获取到装载到servletContext中的 applicationContext对象了.