Servlet 编写过滤器
Filter 的执行时机、链式调用、编码与登录校验的实际写法。
Key Points过滤器在 Servlet 之前执行,可以改请求/响应,也可以直接拦截不调用 chain.doFilter。
过滤器与拦截器的差别
| Interceptor | ||
|---|---|---|
| Spec | Servlet 规范 | Spring MVC |
| Scope | 所有请求,含静态资源 | 只作用于 Controller |
| Access | ServletRequest / Response | Handler、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.过滤器与拦截器的差别
| Filter | Interceptor | |
|---|---|---|
| 规范 | Servlet 规范 | Spring MVC |
| 作用范围 | 所有请求,含静态资源 | 只作用于 Controller |
| 能拿到 | ServletRequest / Response | Handler、ModelAndView |
| 典型用途 | 编码、日志、鉴权、压缩 | 权限注解、参数校验 |
一个过滤器
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 销毁");
}
}chain.doFilter 之前是「请求预处理」,之后是「响应后处理」。不调用它,请求就此中断,后面的一切都不执行——鉴权失败就是靠这个短路。
登录校验过滤器
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);
}
}多个过滤器的顺序
注解方式无法保证顺序,容器按类名或扫描顺序执行,不同容器结果不同。需要确定顺序时必须写 web.xml,按 <filter-mapping> 的出现顺序执行。
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>过滤器里改响应体要用
HttpServletResponseWrapper 包一层,把输出截到内存再改写。直接对 getWriter() 返回的对象操作没有效果,因为内容已经流走了。