SpringBoot中的攔截器江湖
前言
很多小伙伴在工作中遇到攔截需求就無腦寫HandlerInterceptor,結果被復雜場景搞得鼻青臉腫。
作為一名有多年開發經驗的程序員,今天領大家到SpringBoot的山頭認認6把交椅:
圖片
這篇文章以梁山為背景的介紹SpringBoot中的攔截器,可能更通俗易懂。
希望對你會有所幫助,記得點贊和收藏。
第一把交椅:Filter
Filter是梁山中的總寨主。
典型戰斗場面:全局鑒權/接口耗時統計
@WebFilter("/*")
public class CostFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
long start = System.currentTimeMillis();
chain.doFilter(req, res); // 放行江湖令箭
System.out.println("接口耗時:"+(System.currentTimeMillis()-start)+"ms");
}
}
起義緣由:必須是最高寨主,因為他在Servlet容器滾刀肉層面出手。想當年有個兄弟在Filter里調用Spring Bean,結果NPE錯殺千人(要用WebApplicationContextUtils拿Bean才是正解)
第二把交椅:HandlerInterceptor
HandlerInterceptor是梁山中的二當家。
必殺場景:接口權限驗證/請求參數自動裝填
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("X-Token");
if(!"vip666".equals(token)){
response.setStatus(403);
returnfalse; // 關門放狗
}
returntrue;
}
}
// 衙門張貼告示
@Configuration
publicclass WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login");
}
}
二當家的雷區:
- postHandle里修改了Response但內容已提交(注意response.isCommitted()判斷)
- 攔截資源請求要配置靜態路徑排出(例如/exclude/**)
- 多攔截器順序要調準確(Order值越小越早執行)
第三把交椅:AOP攔截器
AOP是梁山中的軍師智多星。
運籌帷幄場景:服務層方法緩存/事務管理
@Aspect
@Component
public class CacheAspect {
@Around("@annotation(com.example.anno.Cacheable)")
public Object aroundCache(ProceedingJoinPoint jp) {
String cacheKey = buildKey(jp);
Object cacheVal = redisTemplate.opsForValue().get(cacheKey);
if(cacheVal != null) return cacheVal;
Object result = jp.proceed();
redisTemplate.opsForValue().set(cacheKey, result, 5, TimeUnit.MINUTES);
return result;
}
}
軍師錦囊:
- 只可攔截Spring管理的Bean(new的對象攔截不了)
- 與Transactional注解的順序要注意(建議AOP切面Order大于事務切面)
- 自定義注解要寫在接口方法上才生效(要是實現類方法需要用@within)
第四把交椅:RestTemplate攔截器
RestTemplate是梁山中的水軍頭領。
遠程戰事:統一添加請求頭/加密請求參數
public class TraceInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) {
request.getHeaders().add("X-TraceId", UUID.randomUUID().toString());
return execution.execute(request, body);
}
}
// 注冊水軍
@Bean
public RestTemplate restTemplate() {
RestTemplate rt = new RestTemplate();
rt.getInterceptors().add(new TraceInterceptor());
return rt;
}
總督黑歷史:
- 編碼問題:body若是字符串需要自行轉字節數組(避免亂碼)
- 多次攔截:攔截器按添加順序執行(第一個最后執行)
- 訪問HTTPS需要額外配置SSL(記得補上SSLContext)
第五把交椅:Feign攔截器
Feign攔截器是梁山中的外交使節。
出使外國:統一簽名計算/Header透傳
public class FeignAuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("Authorization", "Bearer " + SecurityContext.getToken());
}
}
// 締結合約
@Configuration
publicclass FeignConfig {
@Bean
public FeignAuthInterceptor feignAuthInterceptor() {
returnnew FeignAuthInterceptor();
}
}
使節燙手山芋:
- GET請求Body丟失問題(要自己特殊處理)
- Form表單參數要手動編碼(使用feign-form擴展)
- Path參數需要Expression表達式解析(動態值要用@Param注明)
第六把交椅:WebFilter
WebFilter是梁山中的特種兵。
閃電戰場景:響應式編程統一編碼/跨域處理
@Component
public class CorsWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Access-Control-Allow-Origin", "*");
return chain.filter(exchange);
}
}
作戰條件:
- 必須在WebFlux環境下(傳統MVC無效)
- 響應式編程模式(函數式聲明)
- 非阻塞管道(異步要配合Mono/Flux)
各派武功排行榜
門派 | 攻擊范圍 | 招式復雜度 | 內力消耗 | 首選戰場 |
Filter | 全局最外層 | ★★☆☆☆ | 低 | 安全校驗/日志記錄 |
Handler | MVC控制器層 | ★★★☆☆ | 中 | 權限控制 |
AOP | 業務方法級 | ★★★★☆ | 高 | 緩存/事務 |
RestTemplate | HTTP客戶端 | ★★★☆☆ | 中 | 服務間調用 |
Feign | 聲明式客戶端 | ★★★★☆ | 高 | 微服務通信 |
WebFilter | 響應式全鏈路 | ★★★★★ | 極高 | WebFlux應用 |
武林秘笈
1. 順序就是力量
Filter -> Interceptor -> AOP ,越早攔截越省力(但別在Filter里做業務)
2. 量力而行選兵器
- 簡單鑒權用HandlerInterceptor
- 方法級管控上AOP
- 微服務用FeignInterceptor
3. 性能損耗要監控
用Arthas監控攔截鏈路耗時,避免攔截器連環奪命call
# 查看HandlerInterceptor耗時
trace *.preHandle '#cost>10'
# 診斷AOP切面
watch com.example.aop.*Aspect * '{params,returnObj}' -x 3
最后送給各位江湖兒女一句話:
攔截是門藝術,別讓好刀砍了自己人!