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
实例。核心逻辑如下
- 分配Servlet(支持SingleThreadModel Servlet)
- 构建ApplicationFilterChain:基于当前请求的URL和对应的Servlet,筛选出所有匹配的 Filter实例
- 执行过滤器链ApplicationFilterChain.doFilter(内部会在所有Filter执行完毕后再执行Servlet#service方法)
- 资源回收和状态清理,并记录一些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还要设计两套机制呢?核心其实一句话可以总结:职责分离,边界清晰
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 级别的调度和分配(缓存和回收)
而Filter则是标准 Servlet 规范定义的一部分,属于业务开发层面的能力扩展。比如用户鉴权、具体的web资源访问权限限制、业务日志埋点等需求都由业务催生,就完全不该在容器层面(Pipeline + Valve)中去处理
所以核心点:Pipeline + Valve 管容器架构,Filter 管业务,各司其职