Tomcat-责任链之Pipeline+Valve和Filter


​ 分析了Tomcat中Pipeline+Valve和支持Filter的ApplicationFilterChain两种基于责任链模式的实现逻辑,并在最后总结了这两者的异同点以及为什么会有这两种类似的模块。

Pipeline

​ 可以理解为请求传递的管道,每个容器(Engine、Host、Context、Wrapper)都关联一个独立的 Pipeline(管道),用于处理 HTTP 请求的责任链式分发,其默认实现为 StandardPipeline

​ 每个 Pipeline 内部由一系列 Valve(阀门)组成,Pipeline为其提供了一系列接口(包括添加,删除和管理其生命周期管理等),并将它们构造成链式结构(addValve方法将新 Valve 插入到链中 倒数第二个位置,即 basic Valve 之前)

请求到达某个容器时,会先被其 Pipeline 的第一个 Valve(first)处理,然后依次传递到后续的 Valve,最终到达 basic Valve。basic Valve 处理完成后,请求再被传递给下层子容器的 Pipeline,并最终传递到Filter中,从而达到请求贯穿全部容器的作用

核心字段和方法

public class StandardPipeline extends LifecycleBase implements Pipeline {
    // basic valve(last valve),当前管道中最后处理的一个Valve
    protected Valve basic = null;

    // 关联的容器
    protected Container container = null;

    // value链首
    protected Valve first = null;

    public void addValve(Valve valve) {

        // 将容器也绑定搭到valve中
        if (valve instanceof Contained) {
            ((Contained) valve).setContainer(this.container);
        }

        // 如果当前 Pipeline 已处于运行状态,则启动Valve的生命周期start方法
        if (getState().isAvailable()) {
            if (valve instanceof Lifecycle) {
                try {
                    ((Lifecycle) valve).start();
                } catch (LifecycleException e) {
                    log.error(sm.getString("standardPipeline.valve.start"), e);
                }
            }
        }

        if (first == null) {
            first = valve;
            valve.setNext(basic);
        } else {
            // 将valve添加到Valve链中倒数第二个位置(最后一个位置为固定的basic)
            Valve current = first;
            while (current != null) {
                if (current.getNext() == basic) {
                    current.setNext(valve);
                    valve.setNext(basic);
                    break;
                }
                current = current.getNext();
            }
        }

        container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
    }
}

Valve

​ 把http请求看作水流,Valve(阀门)即具有控制和预处理水流的作用。每个 Valve 通过其核心的 invoke 方法对请求 (Request) 和响应 (Response) 进行加工处理,并决定是否将请求继续传递给下一个 Valve。关键Valve如下

StandardEngineValve

​ Engine中Pipeline里的最后一个阀门,主要是负责将请求传递给下一层容器Host的Pipeline中。源码较简单就不分析了

StandardHostValve

​ 不仅是 Host 容器中 Pipeline 的最后一个阀门,同时也负责将 WebappClassLoader 绑定到当前处理请求的 worker 线程上。 这样做可以保证:

在业务代码里通过 worker 线程间接创建的新对象、新类或新线程时,默认使用的都是当前 Context 下的 WebappClassLoader,从而确保整个 Web 应用内部始终使用一致的类加载器,避免类加载混乱问题。

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

    // 获取当前请求对应的Web应用上下文
    Context context = request.getContext();
    if (context == null) {
        // 如果没有找到对应的Context,直接返回404
        if (!response.isError()) {
            response.sendError(404);
        }
        return;
    }

    try {
        // 1. 绑定Web应用的类加载器到当前worker线程
        // 将当前worker线程的上下文类加载器设置为WebappClassLoader,这样后续的类加载和创建新的线程就会使用这个WebappClassLoader
        context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);

        try {
            if (!response.isErrorReportRequired()) {
                // 2. 请求向后续容器传递
                context.getPipeline().getFirst().invoke(request, response);
            }
        } catch (Throwable t) {
            // 异常处理(忽略)
        }

    } finally {
        // 3. 恢复原来的类加载器
        // 将当前worker线程的上下文类加载器恢复为原来的值(commonClassLoader),确保其不会污染其他请求
        context.unbind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
    }
}

StandardContextValve

​ Context中Pipeline里的最后一个阀门,可以注意下对当前context的META-INF和WEB-INF目录访问的限制就是在这个Valve中做的。

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

    // META-INF和WEB-INF目录下的资源不允许访问
    MessageBytes requestPathMB = request.getRequestPathMB();
    if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
            || (requestPathMB.equalsIgnoreCase("/META-INF"))
            || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
            || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }
    // ...

    wrapper.getPipeline().getFirst().invoke(request, response);
}

StandardWrapperValve

​ StandardWrapperValve值得重点分析,它是 Tomcat 容器中请求分发链路的最后一个核心 Valve,其职责是将请求最终交付给具体的 Servlet 实例。核心逻辑如下

  1. 分配Servlet(支持SingleThreadModel Servlet)
  2. 构建ApplicationFilterChain:基于当前请求的URL和对应的Servlet,筛选出所有匹配的 Filter实例
  3. 执行过滤器链ApplicationFilterChain.doFilter(内部会在所有Filter执行完毕后再执行Servlet#service方法)
  4. 资源回收和状态清理,并记录一些jmx的统计信息

final class StandardWrapperValve extends ValveBase {

    // ============== jmx统计使用(毫秒单位)===============
    // 当前servlet的总处理时间
    private volatile long processingTime;
    // 当前servlet一次处理的最大耗时时间
    private volatile long maxTime;
    // 当前servlet一次处理的最小耗时时间
    private volatile long minTime = Long.MAX_VALUE;
    // 请求总数
    private final AtomicInteger requestCount = new AtomicInteger(0);
    // 错误请求数
    private final AtomicInteger errorCount = new AtomicInteger(0);

    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        boolean unavailable = false;
        Throwable throwable = null;
        long t1=System.currentTimeMillis();
        requestCount.incrementAndGet();
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        Servlet servlet = null;
        Context context = (Context) wrapper.getParent();

        // 1. 分配servlet并初始化
        try {
            if (!unavailable) {
                servlet = wrapper.allocate();
            }
        }catch () {
            // ... 异常处理省略
        }

        // 2. 创建ApplicationFilterChain,并匹配合适的filter
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);


        Container container = this.container;
        try {
            if ((servlet != null) && (filterChain != null)) {
               // 3. 执行过滤器链
                filterChain.doFilter(request.getRequest(), response.getResponse());
            }
        } catch () {
            // ... 异常处理省略
        }finally {
            // =========== 4. 资源释放让其可在使用,减少垃圾收集器的活动 ================

            if (filterChain != null) {
                filterChain.release();
            }

            try {
                // servlet归还
                if (servlet != null) {
                    wrapper.deallocate(servlet);
                }
            } catch () {
                // ... 异常处理省略
            }

            try {
                if ((servlet != null) &&
                    (wrapper.getAvailable() == Long.MAX_VALUE)) {
                    wrapper.unload();
                }
            }  catch () {
                // ... 异常处理省略
            }
            long t2=System.currentTimeMillis();
            // 统计耗时,并更新相关数据
            long time=t2-t1;
            processingTime += time;
            if( time > maxTime) {
                maxTime=time;
            }
            if( time < minTime) {
                minTime=time;
            }
        }
    }

}

Filter

ApplicationFilterChain

​ 过滤器链(FilterChain)用于管理多个过滤器(Filter)的执行顺序与截断逻辑,核心机制和 Spring AOP 中的切面调用逻辑类似

每个请求对应一个独立的 FilterChain 实例,内部维护一个 pos 指针,在递归调用时通过传入当前 FilterChain 实例来控制指针的移动,从而实现 Filter 的顺序执行与截断控制。当所有 Filter 执行完毕(指针走到末尾)后,才会调用目标方法(FilterChain 中对应 Servlet#service(),Spring AOP 中则对应原始业务方法);如果中间某个 Filter 没有继续调用 chain.doFilter(),则会直接返回,实现链路截断

​ 整体实现其实就是典型的过滤器(增强器)模型,既能保证 Filter(切面) 之间的执行顺序,又支持灵活的拦截与提前返回

public final class ApplicationFilterChain implements FilterChain {

    // 合适的Filter数组
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

    // 当前执行到的filter位置指针
    private int pos = 0;

    // filter的个数,<= filters.lenght
    private int n = 0;
    private Servlet servlet = null;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {

        internalDoFilter(request, response);
    }

    /**
     *  内部真正执行filter链逻辑
     */
    private void internalDoFilter(ServletRequest request,
            ServletResponse response)
            throws IOException, ServletException {

        // 依次调用下一个filter,直到filter链调用完毕
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();
                // 将当前对象传入,以此触发递归调用
                filter.doFilter(request, response, this);
            } catch (){
                // ... 异常处理
            }
            // 若果某个Filter内部若未调用 chain.doFilter(),则会退回到这以终止链式调用,Servlet 不会被执行
            return;
        }

        // filter链调用完毕,最后再执行servlet的service方法
        try {
            servlet.service(request, response);
        } catch (){
            // ... 异常处理
        }finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
    }
}

对比总结

​ Pipeline+Valve和Filter都是基于责任链模式实现的,看起来功能上高度重叠,甚至都能互相模拟对方的行为,那为什么Tomcat还要设计两套机制呢?核心其实一句话可以总结:职责分离,边界清晰

  1. Pipeline + Valve是 Tomcat 内部容器模型的一部分,完全属于 Tomcat 自己的体系。每个容器都有自己的 Pipeline,也就有自己的 Valve 链,各自的Pipeline也只关心当前容器:

    • Host 层可以做虚拟主机级别的统一控制
    • Context 层可以做应用级别的资源隔离与控制
      • 比方说其实在进入Pipiline之前的CoyoteAdapter里就解析出了当前url需要的Context容器,在这里就可以限制对web项目META-INF和WEB-INF目录的访问权限。但还是在Context的Valve里实现的,这是因为这些目录属于具体 web 项目,Tomcat定义了它们不允许被访问,所以它们的规则校验应当由 Tomcat的Context容器层编写
    • Wrapper 层可以做单个 Servlet 级别的调度和分配(缓存和回收)
  2. 而Filter则是标准 Servlet 规范定义的一部分,属于业务开发层面的能力扩展。比如用户鉴权、具体的web资源访问权限限制、业务日志埋点等需求都由业务催生,就完全不该在容器层面(Pipeline + Valve)中去处理

所以核心点:Pipeline + Valve 管容器架构,Filter 管业务,各司其职