Spring中過濾器(Filter)和攔截器(Interceptor)到底啥區(qū)別詳解
前言
在做 Spring Boot 開發(fā)時,經(jīng)常會聽到兩句話:“用 Filter 做統(tǒng)一處理”、“用 Interceptor 攔截請求”。很多同學會混淆:它們是不是都是 Spring 的?它們在請求鏈路的哪個位置?適合干嘛?Spring Boot 里怎么寫?
這篇文章用簡單的方式講清楚 Filter 和 Interceptor 的定位,并給出兩個能直接復制運行的例子,最后用一個總結(jié)把選擇建議說透。
1. 先下結(jié)論:它們歸屬不同
Filter(過濾器)不是 Spring 發(fā)明的,它屬于 Servlet 規(guī)范,由 Tomcat/Jetty 等容器負責調(diào)用。它的位置更靠外,在請求進入 Spring MVC 之前就會執(zhí)行。
Interceptor(攔截器)通常指 Spring MVC 的 HandlerInterceptor,是 Spring MVC 的能力。它的位置更靠內(nèi),請求進入 DispatcherServlet 之后、Controller 方法執(zhí)行前后才會觸發(fā)。
2. 用“洋蔥模型”理解位置關系
請求從外往內(nèi)走:
Tomcat → Filter(Servlet 規(guī)范)→ DispatcherServlet(Spring MVC 入口)→ Interceptor(Spring MVC)→ Controller
響應返回時,再按相反方向回去。
3. 什么時候用 Filter?什么時候用 Interceptor?
Filter 更適合做“全站通用、跟業(yè)務無關”的事情,例如編碼處理、全局 CORS、XSS 過濾、RequestWrapper(讀 body/改 header)、統(tǒng)一訪問日志、限流、以及安全鏈路相關處理。很多人不知道的是,Spring Security 本質(zhì)上就是一條很長的 FilterChain。
Interceptor 更適合做“跟 Controller/業(yè)務強相關”的事情,例如登錄態(tài)/權限校驗(尤其需要拿到 Controller 方法或注解時)、接口埋點統(tǒng)計每個 Controller 的耗時、統(tǒng)一注入上下文(userId、traceId)并在 afterCompletion 清理 ThreadLocal/MDC、多租戶 tenant 上下文等。
4. 最關鍵區(qū)別:觸發(fā)時機 & 能拿到的信息
| 維度 | Filter | Interceptor |
|---|---|---|
| 歸屬 | Servlet 規(guī)范(容器調(diào)用) | Spring MVC(DispatcherServlet 調(diào)用) |
| 執(zhí)行時機 | 更早(進入 Spring MVC 之前) | 更晚(進入 Spring MVC 之后,Controller 前后) |
| 是否能拿到 Controller 方法 | 不知道具體 handler | 能拿到 handler(方法/注解) |
| 典型用途 | 全局通用處理、安全過濾、包裝 request | 業(yè)務校驗、埋點、上下文管理、權限 |
5. 例子 1:Filter 記錄全站耗時(完整可用)
目標是打印每個請求的 URI、線程名、耗時,并且能直觀看到它在 Controller 前后執(zhí)行。推薦繼承 OncePerRequestFilter,避免一次請求多次執(zhí)行的問題。
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 和后續(xù)處理都結(jié)束后”。
6. 例子 2:Interceptor 統(tǒng)一注入 userId + traceId,并正確清理(完整可用)
這個例子更貼近真實線上:從請求頭/參數(shù)拿 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(); }
}然后實現(xiàn) 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 執(zhí)行
[Interceptor] afterCompletion
[Filter] after chain
這也能直觀看到 Filter 在外層包著整個 Spring MVC,而 Interceptor 在 Spring MVC 內(nèi)部圍繞 Controller。
總結(jié)
Filter 屬于 Servlet 規(guī)范,位置更靠外,更適合做全站通用處理(日志、編碼、CORS、安全過濾、請求包裝等),它只關心 request/response,不知道具體會走哪個 Controller。
Interceptor 屬于 Spring MVC,位置更靠內(nèi),更適合做與 Controller/業(yè)務相關的攔截(權限、埋點、上下文注入與清理、多租戶等),它能拿到 handler(方法/注解),并且可以在 afterCompletion 做統(tǒng)一清理。
寫 Filter 記住圍繞 chain.doFilter 包一層;寫 Interceptor 記住 preHandle 做攔截、afterCompletion 做清理(尤其 ThreadLocal/MDC)。
到此這篇關于Spring中過濾器(Filter)和攔截器(Interceptor)到底啥區(qū)別的文章就介紹到這了,更多相關Spring過濾器Filter和攔截器Interceptor內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
ShardingSphere jdbc實現(xiàn)分庫分表核心概念詳解
這篇文章主要為大家介紹了ShardingSphere jdbc實現(xiàn)分庫分表核心概念詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
使用Java DOM解析器修改XML文件內(nèi)容的操作方法
在Java中,XML文件的解析和修改可以通過多種方法實現(xiàn),其中DOM(Document Object Model)是一種常用的方式,在本文中,我們將介紹如何使用Java DOM解析器修改XML文件中的內(nèi)容,并給出一個具體的示例,需要的朋友可以參考下2024-08-08
SpringCloud之Zuul網(wǎng)關原理及其配置講解
這篇文章主要介紹了SpringCloud之Zuul網(wǎng)關原理及其配置講解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
淺談Storm在zookeeper上的目錄結(jié)構(gòu)
這篇文章主要介紹了淺談Storm在zookeeper上的目錄結(jié)構(gòu)的相關內(nèi)容,涉及storm使用zookeeper的操作以及詳細結(jié)構(gòu)圖,具有一定參考價值,需要的朋友可以了解下。2017-10-10
Java循環(huán)對bean的屬性進行賦值的實現(xiàn)
本文主要介紹了Java循環(huán)對bean的屬性進行賦值,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08

