Spring原始碼閱讀(5.1.0版本)——Contextloaderlistener
阿新 • • 發佈:2018-12-15
目錄
前言
上了大三了,逐漸想保研,現在一邊準備比賽,一邊學習新知識,一邊做著專案,希望自己能扛下去吧,這篇部落格的原始碼來自spring 5.1.0版本,如有錯誤,歡迎指出
結論
Contextloaderlistener幹了下面幾件事
- 初始化web容器(以反射的方式)
- 設定web容器的父容器
- 將web容器以ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE為關鍵字儲存到servletcontext中
- 設定類載入器,以便以後載入資源
- 在web容器中設定servletcontext的引用
- 在web容器中設定配置檔案的位置
- 重新整理web容器
什麼是ContextLoaderListener
這個類負責WebApplicationContext的啟動以及關閉,它們基於servlet的context-param標籤的contextClass以及contextConfigLocation屬性工作的,建立的容器將會以ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE為關鍵字儲存到ServletContext中
原始碼解析
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { /** * Create a new {@code ContextLoaderListener} that will create a web application * context based on the "contextClass" and "contextConfigLocation" servlet * context-params. See {@link ContextLoader} superclass documentation for details on * default values for each. * <p>This constructor is typically used when declaring {@code ContextLoaderListener} * as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is * required. * <p>The created application context will be registered into the ServletContext under * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} * and the Spring application context will be closed when the {@link #contextDestroyed} * lifecycle method is invoked on this listener. * @see ContextLoader * @see #ContextLoaderListener(WebApplicationContext) * @see #contextInitialized(ServletContextEvent) * @see #contextDestroyed(ServletContextEvent) */ public ContextLoaderListener() { } /** * Create a new {@code ContextLoaderListener} with the given application context. This * constructor is useful in Servlet 3.0+ environments where instance-based * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener} * API. * <p>The context may or may not yet be {@linkplain * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it * (a) is an implementation of {@link ConfigurableWebApplicationContext} and * (b) has <strong>not</strong> already been refreshed (the recommended approach), * then the following will occur: * <ul> * <li>If the given context has not already been assigned an {@linkplain * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li> * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to * the application context</li> * <li>{@link #customizeContext} will be called</li> * <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer org.springframework.context.ApplicationContextInitializer ApplicationContextInitializers} * specified through the "contextInitializerClasses" init-param will be applied.</li> * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li> * </ul> * If the context has already been refreshed or does not implement * {@code ConfigurableWebApplicationContext}, none of the above will occur under the * assumption that the user has performed these actions (or not) per his or her * specific needs. * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples. * <p>In any case, the given application context will be registered into the * ServletContext under the attribute name {@link * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring * application context will be closed when the {@link #contextDestroyed} lifecycle * method is invoked on this listener. * @param context the application context to manage * @see #contextInitialized(ServletContextEvent) * @see #contextDestroyed(ServletContextEvent) */ public ContextLoaderListener(WebApplicationContext context) { super(context); } /** * Initialize the root web application context. */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } /** * Close the root web application context. */ @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
保留英文註釋,以便以後閱讀,這裡簡單總結一下各個函式的功能:
- public ContextLoaderListener():預設建構函式,在<listener/>標籤中宣告contextloaderlistener時,就是使用預設建構函式初始化contextloaderlistenener的
- public ContextLoaderListener(WebApplicationContext context):這個函式我自己沒怎麼用過,這裡暫且略過,以後接觸到再來總結
- public void contextInitialized(ServletContextEvent event):初始化web容器,其實最終是通過反射的方式初始化web容器,接下來會總結
- public void contextDestroyed(ServletContextEvent event):關閉web容器
接下來看看容器的初始化過程:initWebApplicationContext方法(在父類ContextLoader中)
/**
* Initialize Spring's web application context for the given servlet context,
* using the application context provided at construction time, or creating a new one
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #ContextLoader(WebApplicationContext)
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
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.
//初始化web容器
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
//ConfigurableWebApplicationContext介面定義了servlet相關的屬性,例如在容器中設定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);
}
//配置以及重新整理web容器,會在web容器中設定servletcontetx的引用,設定web容器配置檔案的位置(包含有bean的資訊),重新整理web容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//在servletContext中以ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 為關鍵字儲存容器
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;
}
}
總結一下,initWebApplicationContext方法幹了下面幾件事
- 初始化web容器(以反射的方式)
- 設定web容器的父容器
- 將web容器以ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE為關鍵字儲存到servletcontext中
- 設定類載入器,以便以後載入資源
- 在web容器中設定servletcontext的引用
- 在web容器中設定配置檔案的位置
- 重新整理web容器
createWebApplicationContext方法會建立web容器,這個方法內部會呼叫determineContextClass方法:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//獲得web容器的類全限定名,如果沒有在配置檔案中配置web容器類,預設使用XmlWebApplicationContext類
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
如果想使用AnnotationConfigApplicationContext,則必須在web.xml中配置,如下:
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
接下來看看BeanUtils類的instantiateClass,這個類會通過反射初始化web容器,同時支援kotlin類
/**
* Convenience method to instantiate a class using the given constructor.
* <p>Note that this method tries to set the constructor accessible if given a
* non-accessible (that is, non-public) constructor, and supports Kotlin classes
* with optional parameters and default values.
* @param ctor the constructor to instantiate
* @param args the constructor arguments to apply (use {@code null} for an unspecified
* parameter if needed for Kotlin classes with optional parameters and default values)
* @return the new instance
* @throws BeanInstantiationException if the bean cannot be instantiated
* @see Constructor#newInstance
*/
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
try {
ReflectionUtils.makeAccessible(ctor);
return (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
}
catch (InstantiationException ex) {
throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
}
catch (IllegalAccessException ex) {
throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
}
catch (IllegalArgumentException ex) {
throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
}
catch (InvocationTargetException ex) {
throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
}
}
注意到ctor.newInstance(args),反射的方法出現了,其實是使用預設建構函式初始化web容器類,由於我不怎麼接觸kotlin,所以這裡就不展開了