水煮Tomcat(八)– 请求的创造与毁灭

57 阅读3分钟

前言

这个系列的最后一章,简单介绍一下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));
        }
    }