SpringBoot中的攔截器江湖
前言
很多小伙伴在工作中遇到攔截需求就無腦寫HandlerInterceptor,結(jié)果被復(fù)雜場景搞得鼻青臉腫。
作為一名有多年開發(fā)經(jīng)驗(yàn)的程序員,今天領(lǐng)大家到SpringBoot的山頭認(rèn)認(rèn)6把交椅:
圖片
這篇文章以梁山為背景的介紹SpringBoot中的攔截器,可能更通俗易懂。
希望對(duì)你會(huì)有所幫助,記得點(diǎn)贊和收藏。
第一把交椅:Filter
Filter是梁山中的總寨主。
典型戰(zhàn)斗場面:全局鑒權(quán)/接口耗時(shí)統(tǒng)計(jì)
@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("接口耗時(shí):"+(System.currentTimeMillis()-start)+"ms");
}
}
起義緣由:必須是最高寨主,因?yàn)樗赟ervlet容器滾刀肉層面出手。想當(dāng)年有個(gè)兄弟在Filter里調(diào)用Spring Bean,結(jié)果NPE錯(cuò)殺千人(要用WebApplicationContextUtils拿Bean才是正解)
第二把交椅:HandlerInterceptor
HandlerInterceptor是梁山中的二當(dāng)家。
必殺場景:接口權(quán)限驗(yàn)證/請(qǐng)求參數(shù)自動(dòng)裝填
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; // 關(guān)門放狗
}
returntrue;
}
}
// 衙門張貼告示
@Configuration
publicclass WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login");
}
}
二當(dāng)家的雷區(qū):
- postHandle里修改了Response但內(nèi)容已提交(注意response.isCommitted()判斷)
- 攔截資源請(qǐng)求要配置靜態(tài)路徑排出(例如/exclude/**)
- 多攔截器順序要調(diào)準(zhǔn)確(Order值越小越早執(zhí)行)
第三把交椅:AOP攔截器
AOP是梁山中的軍師智多星。
運(yùn)籌帷幄場景:服務(wù)層方法緩存/事務(wù)管理
@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的對(duì)象攔截不了)
- 與Transactional注解的順序要注意(建議AOP切面Order大于事務(wù)切面)
- 自定義注解要寫在接口方法上才生效(要是實(shí)現(xiàn)類方法需要用@within)
第四把交椅:RestTemplate攔截器
RestTemplate是梁山中的水軍頭領(lǐng)。
遠(yuǎn)程戰(zhàn)事:統(tǒng)一添加請(qǐng)求頭/加密請(qǐng)求參數(shù)
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);
}
}
// 注冊(cè)水軍
@Bean
public RestTemplate restTemplate() {
RestTemplate rt = new RestTemplate();
rt.getInterceptors().add(new TraceInterceptor());
return rt;
}
總督黑歷史:
- 編碼問題:body若是字符串需要自行轉(zhuǎn)字節(jié)數(shù)組(避免亂碼)
- 多次攔截:攔截器按添加順序執(zhí)行(第一個(gè)最后執(zhí)行)
- 訪問HTTPS需要額外配置SSL(記得補(bǔ)上SSLContext)
第五把交椅:Feign攔截器
Feign攔截器是梁山中的外交使節(jié)。
出使外國:統(tǒng)一簽名計(jì)算/Header透傳
public class FeignAuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("Authorization", "Bearer " + SecurityContext.getToken());
}
}
// 締結(jié)合約
@Configuration
publicclass FeignConfig {
@Bean
public FeignAuthInterceptor feignAuthInterceptor() {
returnnew FeignAuthInterceptor();
}
}
使節(jié)燙手山芋:
- GET請(qǐng)求Body丟失問題(要自己特殊處理)
- Form表單參數(shù)要手動(dòng)編碼(使用feign-form擴(kuò)展)
- Path參數(shù)需要Expression表達(dá)式解析(動(dòng)態(tài)值要用@Param注明)
第六把交椅:WebFilter
WebFilter是梁山中的特種兵。
閃電戰(zhàn)場景:響應(yīng)式編程統(tǒng)一編碼/跨域處理
@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);
}
}
作戰(zhàn)條件:
- 必須在WebFlux環(huán)境下(傳統(tǒng)MVC無效)
- 響應(yīng)式編程模式(函數(shù)式聲明)
- 非阻塞管道(異步要配合Mono/Flux)
各派武功排行榜
門派 | 攻擊范圍 | 招式復(fù)雜度 | 內(nèi)力消耗 | 首選戰(zhàn)場 |
Filter | 全局最外層 | ★★☆☆☆ | 低 | 安全校驗(yàn)/日志記錄 |
Handler | MVC控制器層 | ★★★☆☆ | 中 | 權(quán)限控制 |
AOP | 業(yè)務(wù)方法級(jí) | ★★★★☆ | 高 | 緩存/事務(wù) |
RestTemplate | HTTP客戶端 | ★★★☆☆ | 中 | 服務(wù)間調(diào)用 |
Feign | 聲明式客戶端 | ★★★★☆ | 高 | 微服務(wù)通信 |
WebFilter | 響應(yīng)式全鏈路 | ★★★★★ | 極高 | WebFlux應(yīng)用 |
武林秘笈
1. 順序就是力量
Filter -> Interceptor -> AOP ,越早攔截越省力(但別在Filter里做業(yè)務(wù))
2. 量力而行選兵器
- 簡單鑒權(quán)用HandlerInterceptor
- 方法級(jí)管控上AOP
- 微服務(wù)用FeignInterceptor
3. 性能損耗要監(jiān)控
用Arthas監(jiān)控?cái)r截鏈路耗時(shí),避免攔截器連環(huán)奪命c(diǎn)all
# 查看HandlerInterceptor耗時(shí)
trace *.preHandle '#cost>10'
# 診斷AOP切面
watch com.example.aop.*Aspect * '{params,returnObj}' -x 3
最后送給各位江湖兒女一句話:
攔截是門藝術(shù),別讓好刀砍了自己人!