前言
这个系列的最后一章,简单介绍一下Request里的init和destroy事件。
问题
S: 在日常业务中,可能有这么个场景,请求进到应用之前,需要对其绑定的线程设定一些参数;在请求结束之前,将线程参数抹除。
C: 对于上面这种情况,正常情况下,会考虑使用Filter或者Aspect来对其进行处理;但是过滤器可以对线程设置参数,其destroy方法只会调用一次,没法针对单个线程进行处置;而切面的作用域比较狭窄,仅限于切面内方法的执行,而不能覆盖到过滤器和返回结果增强器等组件。
Q: 是否可以进行组合操作呢?在过滤器里设置,在结果增强器里抹除,还要考虑异常情况,如果出现异常,会跳到统一异常处理,在统一异常处理方法内,也要执行抹除。可能还有未考虑到的情况,可能会有隐藏的bug。那么有没有比较简单易行的方式呢?
A: Tomcat给我们提供了一个事件监听机制,在请求启动和销毁时,触发特定的事件监听。
定义
我们先来看看在Context类中对这两个触发方法的定义
/**
* Notify all {@link javax.servlet.ServletRequestListener}s that a request
* has started.
*
* @param request The request object that will be passed to the listener
* @return <code>true</code> if the listeners fire successfully, else
* <code>false</code>
*/
public boolean fireRequestInitEvent(ServletRequest request);
/**
* Notify all {@link javax.servlet.ServletRequestListener}s that a request
* has ended.
*
* @param request The request object that will be passed to the listener
* @return <code>true</code> if the listeners fire successfully, else
* <code>false</code>
*/
public boolean fireRequestDestroyEvent(ServletRequest request);
什么时候执行?
在StandardHostValve中,有个类似@around切面操作的结构,在下发请求前,触发Request初始化事件;在结果返回之后,触发Request销毁事件。当然,前提是当前请求不是异步的,异步请求会开启独立线程去执行请求,且有超时时间,后续单开一章进行详细介绍。
public final void invoke(Request request, Response response) {
// 选择一个context去处理请求,一个context代表一个应用
Context context = request.getContext();
boolean asyncAtStart = request.isAsync();
try {
// 如果是异步执行的请求,不触发请求初始化事件
if (!asyncAtStart && !context.fireRequestInitEvent(request.getRequest())) {
return;
}
// 执行调用链中,context模块的invoke方法,之后将请求转发到spring MVC的DispatchServlet中。
context.getPipeline().getFirst().invoke(request, response);
if (!request.isAsync() && !asyncAtStart) {
// 触发请求的destroy事件
context.fireRequestDestroyEvent(request.getRequest());
}
} finally {
...
}
}
如何自定义监听器?
在spring环境下,实现ServletRequestListener监听接口,并添加注解:@WebListener;
@Slf4j
@WebListener
public class MyServletRequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
log.info("========request销毁事件");
// to do sth.
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
log.info("========request初始化事件");
// to do sth.
}
}
注解的作用是在spring扫描的时候,将此类进行初始化,下面这个类专门用来注册web监听器。
WebListenerHandler() {
super(WebListener.class);
}
然后在嵌入式的tomcat启动后,将监听器填到到StandContextValve实例中
/**
* {@inheritDoc}
*
* Note that this implementation is not thread safe. If two threads call
* this method concurrently, the result may be either set of listeners or a
* the union of both.
*/
@Override
public void setApplicationEventListeners(Object listeners[]) {
applicationEventListenersList.clear();
if (listeners != null && listeners.length > 0) {
applicationEventListenersList.addAll(Arrays.asList(listeners));
}
}