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

web监听器解析的更多相关文章

  1. 在自定义的web监听器中嵌入web中的定时事件

    在 http://www.cnblogs.com/myadmin/p/4806265.html 中说明了自定义web监听器的一些东西. 本文中的web定时任务也基于上篇文章的自定义web监听器. 新建 ...

  2. Web监听器导图详解(转)

    阅读目录 Web监听器 监听器的分类 Servlet版本与Tomcat版本 getAttribute与getParameter的区别 参考 监听器是JAVA Web开发中很重要的内容,其中涉及到的知识 ...

  3. java Web监听器导图详解

    监听器是JAVA Web开发中很重要的内容,其中涉及到的知识,可以参考下面导图: Web监听器 1 什么是web监听器? web监听器是一种Servlet中的特殊的类,它们能帮助开发者监听web中的特 ...

  4. Web监听器导图详解

    监听器是JAVA Web开发中很重要的内容,其中涉及到的知识,可以参考下面导图: Web监听器 1 什么是web监听器? web监听器是一种Servlet中的特殊的类,它们能帮助开发者监听web中的特 ...

  5. Python的Web编程[0] -> Web客户端[1] -> Web 页面解析

     Web页面解析 / Web page parsing 1 HTMLParser解析 下面介绍一种基本的Web页面HTML解析的方式,主要是利用Python自带的html.parser模块进行解析.其 ...

  6. Web 监听器

    什么事web 监听器? Servlet规范中定义的一种特殊类 用于监听ServletContext.HttpSession和ServletRequest等象的创建与销毁的事件 用监听域对象的属性发生修 ...

  7. Listener(Web监听器、活化、钝化)

    Web监听器 总共有8个 划分成三种类型 定义一个类,实现接口 注册 | 配置监听器 监听三个作用域创建和销毁 request -httpServletRequest session -httpSes ...

  8. web.xml解析

    常用元素及含义 <!-- standalone 定义了外部定义的 DTD 文件的存在性,有效值是 yes和 no --> <?xml version="1.0" ...

  9. java Web监听器实现定时发送邮件

    首先介绍java定时器(java.util.Timer)有定时执行计划任务的功能,通过设定定时器的间隔时间,会自动在此间隔时间后执行预先安排好的任务(java.util. TimerTask) 由于我 ...

随机推荐

  1. 155_模型_Power BI & Power Pivot 进销存之安全库存

    155_模型_Power BI & Power Pivot 进销存之安全库存 一.背景 谈进销存的概念时,我们也需要提及另外一个概念:安全库存. 库存周转在理想的状态下是做到零库存,但是在内部 ...

  2. JUnit 5 - Nested Test 内嵌测试

    本文地址:https://www.cnblogs.com/hchengmx/p/15158658.html 1. Nested用来解决什么问题 简单地说,Nested用来解决,随着Case越来越多,C ...

  3. 3.对互斥事件和条件概率的相互理解《zobol的考研概率论教程》

    tag:这篇文章没太多思考的地方,就是做个过渡 1.从条件概率来定义互斥和对立事件 2.互斥事件是独立事件吗? 3.每个样本点都可以看作是互斥事件,来重新看待条件概率 一.从条件概率来定义互斥和对立事 ...

  4. 你不会用node 操作mysql数据库吗?

    http://static.runoob.com/download/websites.sql这是实例 websites.sql文件1.安装node的mysql服务 npm install mysql ...

  5. Gitlab + Gitlab runner + Window powershell

    需求说明 根据领导要求,要把python 项目移到Gitlab 进行管理,并利用Gitlab CI/CD 进行自动化测试,打包,部署.(听起来很简单吧) 比较头大,完全没有经验,python 也是刚上 ...

  6. SAP Container Controls(容器)

    BC_CONTROLS_TUTORIAL 效果 代码 REPORT bc_controls_tutorial. *------------------------------------------- ...

  7. Python收集这些视频只是单纯的想做做壁纸,大家不要误会

    首先澄清一下,我用Python收集这些视频,绝不是想做别的什么,真的只是用来做动态壁纸,大家不要误会!我不是那样的人~ 这样的不过份吧 (这个动图看不看的到就看有没有缘分了 ) 阅读本文你需要准备 1 ...

  8. C#生成putty格式的ppk文件(支持passphrase)

    背景 2022国家级护网行动即将开启,根据阿里云给出的安全建议,需要将登陆Linux的方式改为密钥对方式.我这里使用的远程工具是自己开发的,能够同时管理Windows和Linux,但是以前不支持密钥对 ...

  9. 方法引用(Method References)

    * 方法引用的使用 * * 1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用! * * 2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口 ...

  10. letsencrypt更换pip源

    vim letsencrypt-auto 将DEFAULT_INDEX_BASE = 'https://pypi.python.org'改为DEFAULT_INDEX_BASE = 'http://m ...