前言: 這是關於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對象了.