前言
在做 spring boot 开发时,经常会听到两句话:“用 filter 做统一处理”、“用 interceptor 拦截请求”。很多同学会混淆:它们是不是都是 spring 的?它们在请求链路的哪个位置?适合干嘛?spring boot 里怎么写?
这篇文章用简单的方式讲清楚 filter 和 interceptor 的定位,并给出两个能直接复制运行的例子,最后用一个总结把选择建议说透。
1. 先下结论:它们归属不同
filter(过滤器)不是 spring 发明的,它属于 servlet 规范,由 tomcat/jetty 等容器负责调用。它的位置更靠外,在请求进入 spring mvc 之前就会执行。
interceptor(拦截器)通常指 spring mvc 的 handlerinterceptor,是 spring mvc 的能力。它的位置更靠内,请求进入 dispatcherservlet 之后、controller 方法执行前后才会触发。
2. 用“洋葱模型”理解位置关系
请求从外往内走:
tomcat → filter(servlet 规范)→ dispatcherservlet(spring mvc 入口)→ interceptor(spring mvc)→ controller
响应返回时,再按相反方向回去。
3. 什么时候用 filter?什么时候用 interceptor?
filter 更适合做“全站通用、跟业务无关”的事情,例如编码处理、全局 cors、xss 过滤、requestwrapper(读 body/改 header)、统一访问日志、限流、以及安全链路相关处理。很多人不知道的是,spring security 本质上就是一条很长的 filterchain。
interceptor 更适合做“跟 controller/业务强相关”的事情,例如登录态/权限校验(尤其需要拿到 controller 方法或注解时)、接口埋点统计每个 controller 的耗时、统一注入上下文(userid、traceid)并在 aftercompletion 清理 threadlocal/mdc、多租户 tenant 上下文等。
4. 最关键区别:触发时机 & 能拿到的信息
| 维度 | filter | interceptor |
|---|---|---|
| 归属 | servlet 规范(容器调用) | spring mvc(dispatcherservlet 调用) |
| 执行时机 | 更早(进入 spring mvc 之前) | 更晚(进入 spring mvc 之后,controller 前后) |
| 是否能拿到 controller 方法 | 不知道具体 handler | 能拿到 handler(方法/注解) |
| 典型用途 | 全局通用处理、安全过滤、包装 request | 业务校验、埋点、上下文管理、权限 |
5. 例子 1:filter 记录全站耗时(完整可用)
目标是打印每个请求的 uri、线程名、耗时,并且能直观看到它在 controller 前后执行。推荐继承 onceperrequestfilter,避免一次请求多次执行的问题。
import jakarta.servlet.filterchain;
import jakarta.servlet.servletexception;
import jakarta.servlet.http.httpservletrequest;
import jakarta.servlet.http.httpservletresponse;
import org.springframework.stereotype.component;
import org.springframework.web.filter.onceperrequestfilter;
import java.io.ioexception;
@component
public class accesslogfilter extends onceperrequestfilter {
@override
protected void dofilterinternal(
httpservletrequest request,
httpservletresponse response,
filterchain filterchain
) throws servletexception, ioexception {
long start = system.currenttimemillis();
string uri = request.getrequesturi();
string thread = thread.currentthread().getname();
try {
system.out.println("[filter] before chain, uri=" + uri + ", thread=" + thread);
filterchain.dofilter(request, response); // 放行,进入 dispatcherservlet / controller
} finally {
long cost = system.currenttimemillis() - start;
system.out.println("[filter] after chain , uri=" + uri + ", cost=" + cost + "ms");
}
}
}理解要点是:filterchain.dofilter 前是“进 spring mvc 之前”,dofilter 后是“controller 和后续处理都结束后”。
6. 例子 2:interceptor 统一注入 userid + traceid,并正确清理(完整可用)
这个例子更贴近真实线上:从请求头/参数拿 userid,生成 traceid,放到 threadlocal,controller 里随时能取,同时在 aftercompletion 清理,避免线程池复用导致串号和“线程级常驻”。
先定义一个上下文对象和 threadlocal 容器。
requestcontext.java:
public class requestcontext {
private final string userid;
private final string traceid;
public requestcontext(string userid, string traceid) {
this.userid = userid;
this.traceid = traceid;
}
public string getuserid() { return userid; }
public string gettraceid() { return traceid; }
}requestcontextholder.java:
public class requestcontextholder {
private static final threadlocal<requestcontext> ctx = new threadlocal<>();
public static void set(requestcontext ctx) { ctx.set(ctx); }
public static requestcontext get() { return ctx.get(); }
public static void remove() { ctx.remove(); }
}然后实现 interceptor。
import jakarta.servlet.http.httpservletrequest;
import jakarta.servlet.http.httpservletresponse;
import org.springframework.web.servlet.handlerinterceptor;
import java.util.uuid;
public class contextinterceptor implements handlerinterceptor {
@override
public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) {
string userid = request.getheader("x-userid");
if (userid == null || userid.isblank()) {
userid = request.getparameter("userid");
}
if (userid == null || userid.isblank()) {
userid = "anonymous";
}
string traceid = uuid.randomuuid().tostring().replace("-", "");
requestcontextholder.set(new requestcontext(userid, traceid));
system.out.println("[interceptor] prehandle, userid=" + userid + ", traceid=" + traceid);
return true;
}
@override
public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler, exception ex) {
requestcontextholder.remove();
system.out.println("[interceptor] aftercompletion, cleaned");
}
}接着在 spring boot 中注册 interceptor。
import org.springframework.context.annotation.configuration;
import org.springframework.web.servlet.config.annotation.*;
@configuration
public class webconfig implements webmvcconfigurer {
@override
public void addinterceptors(interceptorregistry registry) {
registry.addinterceptor(new contextinterceptor())
.addpathpatterns("/**")
.excludepathpatterns("/health");
}
}最后写一个 controller 验证上下文是否能拿到。
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.restcontroller;
@restcontroller
public class democontroller {
@getmapping("/demo")
public string demo() {
requestcontext ctx = requestcontextholder.get();
return "ok, userid=" + ctx.getuserid() + ", traceid=" + ctx.gettraceid();
}
}访问 /demo?userid=mm,你会看到输出顺序大致是:
[filter] before chain
[interceptor] prehandle
controller 执行
[interceptor] aftercompletion
[filter] after chain
这也能直观看到 filter 在外层包着整个 spring mvc,而 interceptor 在 spring mvc 内部围绕 controller。
总结
filter 属于 servlet 规范,位置更靠外,更适合做全站通用处理(日志、编码、cors、安全过滤、请求包装等),它只关心 request/response,不知道具体会走哪个 controller。
interceptor 属于 spring mvc,位置更靠内,更适合做与 controller/业务相关的拦截(权限、埋点、上下文注入与清理、多租户等),它能拿到 handler(方法/注解),并且可以在 aftercompletion 做统一清理。
写 filter 记住围绕 chain.dofilter 包一层;写 interceptor 记住 prehandle 做拦截、aftercompletion 做清理(尤其 threadlocal/mdc)。
到此这篇关于spring中过滤器(filter)和拦截器(interceptor)到底啥区别的文章就介绍到这了,更多相关spring过滤器filter和拦截器interceptor内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论