监听器是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();
}