OncePerRequestFilter 的作用

在 Spring 框架中,Filter 默认都继承自 OncePerRequestFilter。为什么要这样设计呢?

顾名思义,OncePerRequestFilter 能够确保在一次请求中只通过一次 Filter,避免重复执行。

核心机制

以下是 OncePerRequestFilter 的核心源码实现:

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
        boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
        
        if (!hasAlreadyFilteredAttribute && !this.skipDispatch(httpRequest) && !this.shouldNotFilter(httpRequest)) {
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                this.doFilterInternal(httpRequest, httpResponse, filterChain);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        } else {
            filterChain.doFilter(request, response);
        }

    } else {
        throw new ServletException("OncePerRequestFilter just supports HTTP requests");
    }
}

通过代码可以看出,它通过检查请求属性(alreadyFilteredAttributeName)来判断当前请求是否已经被过滤过。如果未过滤,则设置标记并执行 doFilterInternal;如果已存在标记,则直接跳过,从而保证单次执行。

设计初衷:容器兼容性

通常我们认为,一次请求原本就只会经过过滤器一次,为什么还要特意限定呢?

实际上,这一设计是为了兼容不同的 Web 容器(例如遵循 JSR168 规范的容器)而特意实现的。并不是所有的容器都像我们期望的那样只过滤一次,不同的 Servlet 规范版本,其表现也存在差异。

正如该类的 Javadoc 所述:

/**
 * Filter base class that guarantees to be just executed once per request,
 * on any servlet container. It provides a {@link #doFilterInternal}
 * method with HttpServletRequest and HttpServletResponse arguments.
 *
 * <p>The {@link #getAlreadyFilteredAttributeName} method determines how
 * to identify that a request is already filtered. The default implementation
 * is based on the configured name of the concrete filter instance.
 *
 * @author Juergen Hoeller
 * @since 06.12.2003
 */

Servlet 规范差异示例

不同的 Servlet 版本在处理转发(forward)和包含(include)时行为不同:

  • Servlet 2.3:Filter 会过滤一切请求,包括服务器内部使用 forward 转发的请求和 <%@ include file="/index.jsp" %> 的情况。
  • Servlet 2.4:Filter 默认只拦截外部提交的请求,forwardinclude 等内部转发都不会被过滤。但在某些场景下,我们需要在转发时也用到 Filter。

结论

因此,为了兼容各种不同的运行环境和 Servlet 规范版本,默认让 Filter 继承 OncePerRequestFilter 是一个比较稳妥的选择。

说明:文中提到的 Servlet 2.3/2.4 属于较早的规范版本,现代 Servlet 容器(如 Tomcat 8+/9+ 对应 Servlet 3.0/4.0+)行为已趋于一致,但 Spring 保留此设计仍是为了确保跨容器的一致性与健壮性。