Servlet 编写过滤器

Filter 的执行时机、链式调用、编码与登录校验的实际写法。

Intermediate
Key Points过滤器在 Servlet 之前执行,可以改请求/响应,也可以直接拦截不调用 chain.doFilter。

过滤器与拦截器的差别

Interceptor
SpecServlet 规范Spring MVC
Scope所有请求,含静态资源只作用于 Controller
AccessServletRequest / ResponseHandler、ModelAndView
Typical编码、日志、鉴权、压缩权限注解、参数校验

一个过滤器

Java
import java.io.IOException;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;

@WebFilter(urlPatterns = "/*")
public class EncodingFilter implements Filter {

    @Override
    public void init(FilterConfig cfg) {
        System.out.println("EncodingFilter 初始化");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest r = (HttpServletRequest) req;
        r.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");

        long t0 = System.nanoTime();
        chain.doFilter(req, resp);                 // 放行,交给下一个过滤器或 Servlet
        long ms = (System.nanoTime() - t0) / 1_000_000;
        System.out.printf("%s %s -> %d ms%n", r.getMethod(), r.getRequestURI(), ms);
    }

    @Override
    public void destroy() {
        System.out.println("EncodingFilter 销毁");
    }
}

Code before chain.doFilter pre-processes the request; code after post-processes the response. Skip the call and the request stops right there — that is how an auth failure short-circuits.

登录校验过滤器

Java
@WebFilter(urlPatterns = {"/admin/*", "/user/*"})
public class AuthFilter implements Filter {

    private static final Set<String> WHITE = Set.of("/user/login", "/user/register");

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest r = (HttpServletRequest) req;
        HttpServletResponse w = (HttpServletResponse) resp;
        String path = r.getRequestURI().substring(r.getContextPath().length());

        if (WHITE.contains(path)) {
            chain.doFilter(req, resp);
            return;
        }
        if (r.getSession(false) == null || r.getSession().getAttribute("uid") == null) {
            w.sendRedirect(r.getContextPath() + "/user/login?from=" + path);
            return;                                 // 不调 chain,请求终止
        }
        chain.doFilter(req, resp);
    }
}

多个过滤器的顺序

Annotations give no ordering guarantee — the container may use class name or scan order. When order matters, declare filters in web.xml; they run in the order the <filter-mapping> elements appear.

XML
<filter-mapping>
  <filter-name>encodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>authFilter</filter-name>
  <url-pattern>/admin/*</url-pattern>
</filter-mapping>
To rewrite the response body inside a filter, wrap it in an HttpServletResponseWrapper and buffer the output first. Writing to the original getWriter() does nothing — the bytes have already left.

Friends & Partners