web監聽器解析
監聽器是web三大組件之一,事件監聽機制如下:
- 事件:某個事件,如果初始化上下文
- 事件源:事件發生的地方
- 監聽器:一個對象,擁有需要執行的邏輯
- 註冊監聽:將事件、事件源、監聽器綁定在一起。當事件源發生某個事件後,將事件傳遞給監聽器,監聽器執行相應程式碼邏輯
添加監聽器
在web項目中的web.xml配置文件中一般有這樣一段程式碼,ContextLoaderListener是一個監聽器實現了ServletContextListener介面。在後續解析web.xml文件中會添加到StandardContext上下文的applicationListeners數組中,之後當上下文開啟後會根據監聽器的全限定名構造監聽器實例並初始化監聽器。
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

ServletContextListener能在web應用程式初始化的過程中收到通知,在過濾器filter和servlet初始化之前執行相應的上下文初始化邏輯,也能在servlet上下文關閉時收到通知,在過濾器filter和servlet銷毀後執行相應的上下文銷毀邏輯。
public interface ServletContextListener extends EventListener {
/**
** Notification that the web application initialization process is starting.
* All ServletContextListeners are notified of context initialization before
* any filter or servlet in the web application is initialized.
* @param sce Information about the ServletContext that was initialized
*/
public void contextInitialized(ServletContextEvent sce);
/**
** Notification that the servlet context is about to be shut down. All
* servlets and filters have been destroy()ed before any
* ServletContextListeners are notified of context destruction.
* @param sce Information about the ServletContext that was destroyed
*/
public void contextDestroyed(ServletContextEvent sce);
}
初始化監聽器
當StandardContext上下文啟動後會調用listenerStart方法,該方法初始化所有添加到context的監聽器,之後構建事件執行ServletContextListener類型的監聽器listener.contextInitialized(event);初始化上下文
public boolean listenerStart() {
if (log.isDebugEnabled()) {
log.debug("Configuring application event listeners");
}
// Instantiate the required listeners
// 註冊監聽器的全限定名數組
String listeners[] = findApplicationListeners();
Object results[] = new Object[listeners.length];
boolean ok = true;
for (int i = 0; i < results.length; i++) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(" Configuring event listener class '" +
listeners[i] + "'");
}
try {
String listener = listeners[i];
// 實例化監聽器
results[i] = getInstanceManager().newInstance(listener);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString(
"standardContext.applicationListener", listeners[i]), t);
ok = false;
}
}
if (!ok) {
getLogger().error(sm.getString("standardContext.applicationSkipped"));
return false;
}
// Sort listeners in two arrays
List<Object> eventListeners = new ArrayList<>();
List<Object> lifecycleListeners = new ArrayList<>();
// 分組 區分事件監聽器和生命周期監聽器
// ServletContextListener為生命周期監聽器
for (Object result : results) {
if ((result instanceof ServletContextAttributeListener)
|| (result instanceof ServletRequestAttributeListener)
|| (result instanceof ServletRequestListener)
|| (result instanceof HttpSessionIdListener)
|| (result instanceof HttpSessionAttributeListener)) {
eventListeners.add(result);
}
if ((result instanceof ServletContextListener)
|| (result instanceof HttpSessionListener)) {
lifecycleListeners.add(result);
}
}
// Listener instances may have been added directly to this Context by
// ServletContextInitializers and other code via the pluggability APIs.
// Put them these listeners after the ones defined in web.xml and/or
// annotations then overwrite the list of instances with the new, full
// list.
// 設置應用的事件監聽器和生命周期監聽器
eventListeners.addAll(Arrays.asList(getApplicationEventListeners()));
setApplicationEventListeners(eventListeners.toArray());
for (Object lifecycleListener: getApplicationLifecycleListeners()) {
lifecycleListeners.add(lifecycleListener);
if (lifecycleListener instanceof ServletContextListener) {
noPluggabilityListeners.add(lifecycleListener);
}
}
setApplicationLifecycleListeners(lifecycleListeners.toArray());
// Send application start events
if (getLogger().isDebugEnabled()) {
getLogger().debug("Sending application start events");
}
// Ensure context is not null
getServletContext();
// 不允許設置新的監聽
context.setNewServletContextListenerAllowed(false);
Object instances[] = getApplicationLifecycleListeners();
if (instances == null || instances.length == 0) {
return ok;
}
// 根據servlet上下文構建ServletContextEvent事件
ServletContextEvent event = new ServletContextEvent(getServletContext());
ServletContextEvent tldEvent = null;
if (noPluggabilityListeners.size() > 0) {
noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
tldEvent = new ServletContextEvent(noPluggabilityServletContext);
}
// 調用ServletContextListener監聽器
for (Object instance : instances) {
if (!(instance instanceof ServletContextListener)) {
continue;
}
ServletContextListener listener = (ServletContextListener) instance;
try {
fireContainerEvent("beforeContextInitialized", listener);
if (noPluggabilityListeners.contains(listener)) {
listener.contextInitialized(tldEvent);
} else {
// 容器上下文初始化 調用監聽器contextInitialized方法初始化
listener.contextInitialized(event);
}
fireContainerEvent("afterContextInitialized", listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
fireContainerEvent("afterContextInitialized", listener);
getLogger().error(sm.getString("standardContext.listenerStart",
instance.getClass().getName()), t);
ok = false;
}
}
return ok;
}
ContextLoaderListener解析
ContextLoaderListener上下文載入監聽器有什麼作用呢?它是spring的一個類,主要用來初始化spring容器XmlWebApplicationContext。我們也可以用contextClass指定上下文類使用支援掃描註解的AnnotationConfigWebApplicationContext,如下:
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.monian.study.config.AppConfig</param-value>
</context-param>
@Configuration
@ComponentScan(basePackages = "com.monian.study")
@PropertySource(value = {"classpath:oss.properties", "classpath:datasource.properties"})
public class AppConfig {
}
當執行contextInitialized方法時初始化根web應用上下文:根據contextClass配置的上下文類AnnotationConfigWebApplicationContext若沒有配置則默認XmlWebApplicationContext進行類載入後再通過反射實例化web容器,接著對web容器容器id更新、配置文件路徑設置等初始化操作,最後調用refresh()方法刷新容器,將AppConfig 作為初始java配置文件,掃描basePackages包下的所有類解析spring bean構建spring容器
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE屬性為空,不為空說明可能有重複 報錯
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!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
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.
// 創建contextClass指定的web上下文
if (this.context == null) {
this.context = createWebApplicationContext(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);
}
// 配置&刷新容器
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.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// 默認相同 重新設置容器上下文標識
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
// 從servlet上下文獲取contextId
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id... 設置上下文id
// WebApplicationContext:+contextPath
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 設置servlet上下文
wac.setServletContext(sc);
// 獲取配置文件路徑contextConfigLocation
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
// 設置contextConfigLocation
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
// 初始化環境配置
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
// 初始化servlet屬性資源
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 自定義初始化
customizeContext(sc, wac);
// 刷新web容器 初始化bean
wac.refresh();
}

