在Java Web App中,除了Servlet、Filter和HttpSession外,还有一种Listener组件,用于事件监听。
Listener是Java Web App中的一种事件监听机制,用于监听Web应用程序中产生的事件,例如,在ServletContext
初始化完成后,会触发contextInitialized
事件,实现了ServletContextListener
接口的Listener就可以接收到事件通知,可以在内部做一些初始化工作,如加载配置文件,初始化数据库连接池等。实现了HttpSessionListener
接口的Listener可以接收到Session的创建和消耗事件,这样就可以统计在线用户数。
Listener机制是基于观察者模式实现的,即当某个事件发生时,Listener会接收到通知并执行相应的操作。
Servlet规范定义了很多种Listener接口,常用的Listener包括:
ServletContextListener
:用于监听ServletContext
的创建和销毁事件;HttpSessionListener
:用于监听HttpSession
的创建和销毁事件;ServletRequestListener
:用于监听ServletRequest
的创建和销毁事件;ServletContextAttributeListener
:用于监听ServletContext
属性的添加、修改和删除事件;HttpSessionAttributeListener
:用于监听HttpSession
属性的添加、修改和删除事件;ServletRequestAttributeListener
:用于监听ServletRequest
属性的添加、修改和删除事件。下面我们就来实现上述常用的Listener。
首先我们需要在ServletContextImpl
中注册并管理所有的Listener,所以用不同的List
持有注册的Listener:
public class ServletContextImpl implements ServletContext {
...
private List<ServletContextListener> servletContextListeners = null;
private List<ServletContextAttributeListener> servletContextAttributeListeners = null;
private List<ServletRequestListener> servletRequestListeners = null;
private List<ServletRequestAttributeListener> servletRequestAttributeListeners = null;
private List<HttpSessionAttributeListener> httpSessionAttributeListeners = null;
private List<HttpSessionListener> httpSessionListeners = null;
...
}
然后,实现ServletContext
的addListener()
接口,用于注册Listener:
public class ServletContextImpl implements ServletContext {
...
@Override
public void addListener(String className) {
addListener(Class.forName(className));
}
@Override
public void addListener(Class<? extends EventListener> clazz) {
addListener(clazz.newInstance());
}
@Override
public <T extends EventListener> void addListener(T t) {
// 根据Listener类型放入不同的List:
if (t instanceof ServletContextListener listener) {
if (this.servletContextListeners == null) {
this.servletContextListeners = new ArrayList<>();
}
this.servletContextListeners.add(listener);
} else if (t instanceof ServletContextAttributeListener listener) {
if (this.servletContextAttributeListeners == null) {
this.servletContextAttributeListeners = new ArrayList<>();
}
this.servletContextAttributeListeners.add(listener);
} else if ...
...代码略...
} else {
throw new IllegalArgumentException("Unsupported listener: " + t.getClass().getName());
}
}
...
}
接下来,就是在合适的时机,触发这些Listener。以ServletContextAttributeListener
为例,统一触发的方法放在ServletContextImpl
中:
public class ServletContextImpl implements ServletContext {
...
void invokeServletContextAttributeAdded(String name, Object value) {
logger.info("invoke ServletContextAttributeAdded: {} = {}", name, value);
if (this.servletContextAttributeListeners != null) {
var event = new ServletContextAttributeEvent(this, name, value);
for (var listener : this.servletContextAttributeListeners) {
listener.attributeAdded(event);
}
}
}
void invokeServletContextAttributeRemoved(String name, Object value) {
logger.info("invoke ServletContextAttributeRemoved: {} = {}", name, value);
if (this.servletContextAttributeListeners != null) {
var event = new ServletContextAttributeEvent(this, name, value);
for (var listener : this.servletContextAttributeListeners) {
listener.attributeRemoved(event);
}
}
}
void invokeServletContextAttributeReplaced(String name, Object value) {
logger.info("invoke ServletContextAttributeReplaced: {} = {}", name, value);
if (this.servletContextAttributeListeners != null) {
var event = new ServletContextAttributeEvent(this, name, value);
for (var listener : this.servletContextAttributeListeners) {
listener.attributeReplaced(event);
}
}
}
...
}
当Web App的任何组件调用ServletContext
的setAttribute()
或removeAttribute()
时,就可以触发事件通知:
public class ServletContextImpl implements ServletContext {
...
@Override
public void setAttribute(String name, Object value) {
if (value == null) {
removeAttribute(name);
} else {
Object old = this.attributes.setAttribute(name, value);
if (old == null) {
// 触发attributeAdded:
this.invokeServletContextAttributeAdded(name, value);
} else {
// 触发attributeReplaced:
this.invokeServletContextAttributeReplaced(name, value);
}
}
}
@Override
public void removeAttribute(String name) {
Object old = this.attributes.removeAttribute(name);
// 触发attributeRemoved:
this.invokeServletContextAttributeRemoved(name, old);
}
...
}
其他事件触发也是类似的写法,此处不再重复。
为了测试Listener机制是否生效,我们还需要先编写不同类型的Listener,例如,HelloHttpSessionAttributeListener
实现如下:
@WebListener
public class HelloHelloHttpSessionAttributeListener implements HttpSessionAttributeListener {
final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
logger.info(">>> HttpSession attribute added: {} = {}", event.getName(), event.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
logger.info(">>> HttpSession attribute removed: {} = {}", event.getName(), event.getValue());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
logger.info(">>> HttpSession attribute replaced: {} = {}", event.getName(), event.getValue());
}
}
然后在HttpConnector
中注册所有的Listener:
List<Class<? extends EventListener>> listenerClasses = List.of(HelloHttpSessionAttributeListener.class, ...);
for (Class<? extends EventListener> listenerClass : listenerClasses) {
this.servletContext.addListener(listenerClass);
}
启动服务器,在浏览器中登录或登出,观察日志输出,在每个请求处理前后,可以看到ServletRequestListener
的创建和销毁事件:
08:58:23.944 [HTTP-Dispatcher] INFO c.i.j.e.l.HelloServletRequestListener -- >>> ServletRequest initialized: HttpServletRequestImpl@71a49a97[GET:/]
...
08:58:24.008 [HTTP-Dispatcher] INFO c.i.j.e.l.HelloServletRequestListener -- >>> ServletRequest destroyed: HttpServletRequestImpl@71a49a97[GET:/]
在第一次访问页面和登出时,可以看到HttpSessionListener
的创建和销毁事件:
08:58:23.947 [HTTP-Dispatcher] INFO c.i.j.e.l.HelloHttpSessionListener -- >>> HttpSession created: com.itranswarp.jerrymouse.engine.HttpSessionImpl@15037a31
...
08:58:36.766 [HTTP-Dispatcher] INFO c.i.j.e.l.HelloHttpSessionListener -- >>> HttpSession destroyed: com.itranswarp.jerrymouse.engine.HttpSessionImpl@15037a31
其他事件的触发也可以在日志中找到,这说明我们成功地实现了Servlet规范的Listener机制。
Servlet规范定义了各种Listener组件,我们支持了其中常用的大部分EventListener
组件;
Listener组件由ServletContext
统一管理,并提供统一调度入口方法;
通知机制允许多线程同时调用,如果要防止并发调用Listener的回调方法,需要Listener组件本身在内部做好同步。