Spring?Boot?攔截器幫助解鎖5種常用場(chǎng)景(推薦)
一、Spring Boot中攔截器是什么
在Spring Boot中,攔截器(Interceptor)是一種基于A(yíng)OP(面向切面編程)思想的組件,用于在請(qǐng)求處理前后插入自定義邏輯,實(shí)現(xiàn)權(quán)限校驗(yàn)、日志記錄、性能監(jiān)控等非業(yè)務(wù)功能。
其核心作用是在不修改業(yè)務(wù)代碼的前提下,對(duì)請(qǐng)求進(jìn)行統(tǒng)一處理,類(lèi)似于Servlet中的Filter,但更貼近Spring MVC的體系。
二、攔截器與過(guò)濾器的區(qū)別

三、攔截器主要方法
在實(shí)現(xiàn)攔截器時(shí),HandlerInterceptor接口提供了三個(gè)主要方法,它們?cè)谡?qǐng)求處理的不同階段發(fā)揮著重要作用。
preHandle
這個(gè)方法在請(qǐng)求進(jìn)入Controller之前被調(diào)用 。它的返回值是一個(gè)布爾類(lèi)型,如果返回true,請(qǐng)求將繼續(xù)被處理,進(jìn)入Controller。
如果返回false,請(qǐng)求將被攔截,后續(xù)的Controller方法將不會(huì)被執(zhí)行。在這個(gè)方法中,我們通??梢赃M(jìn)行一些前置的處理操作,比如用戶(hù)認(rèn)證、權(quán)限校驗(yàn)、日志記錄等。?
postHandle
當(dāng)Controller方法執(zhí)行完畢后,視圖渲染之前,這個(gè)方法會(huì)被調(diào)用 。
在這個(gè)方法中,我們可以對(duì)ModelAndView進(jìn)行一些操作,比如添加額外的模型數(shù)據(jù),修改視圖名稱(chēng)等。需要注意的是,如果在preHandle方法中返回了false,這個(gè)方法將不會(huì)被執(zhí)行。?
afterCompletion
在整個(gè)請(qǐng)求處理完成,包括視圖渲染之后,這個(gè)方法會(huì)被調(diào)用。無(wú)論請(qǐng)求處理過(guò)程中是否發(fā)生異常,只要preHandle方法返回true,這個(gè)方法就會(huì)被執(zhí)行。
我們可以在這個(gè)方法中進(jìn)行一些資源清理、記錄日志等收尾工作。如果請(qǐng)求處理過(guò)程中發(fā)生了異常,異常信息會(huì)通過(guò) ex 參數(shù)傳遞進(jìn)來(lái),我們可以根據(jù)這個(gè)參數(shù)進(jìn)行相應(yīng)的異常處理。
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在請(qǐng)求處理之前執(zhí)行的邏輯
System.out.println("preHandle被調(diào)用,請(qǐng)求即將進(jìn)入Controller");
return true; // 返回true表示放行請(qǐng)求,返回false則攔截請(qǐng)求
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在請(qǐng)求處理之后,視圖渲染之前執(zhí)行的邏輯
System.out.println("postHandle被調(diào)用,Controller方法已執(zhí)行完畢,視圖即將渲染");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整個(gè)請(qǐng)求處理完成,包括視圖渲染之后執(zhí)行的邏輯
System.out.println("afterCompletion被調(diào)用,整個(gè)請(qǐng)求已處理完畢");
}
}@Configuration
public class WebMvcConfig1 implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**") // 攔截所有請(qǐng)求
.excludePathPatterns("/static/**"); // 排除/static目錄下的靜態(tài)資源請(qǐng)求
}
}四、5 種常見(jiàn)的攔截器使用場(chǎng)景
1.用戶(hù)認(rèn)證攔截器
用戶(hù)認(rèn)證攔截器的作用就是在用戶(hù)請(qǐng)求進(jìn)入 Controller 之前,驗(yàn)證用戶(hù)的登錄狀態(tài)。如果用戶(hù)已經(jīng)登錄,允許請(qǐng)求繼續(xù)處理。如果用戶(hù)未登錄,則返回錯(cuò)誤信息或者重定向到登錄頁(yè)面。
@Component
public class JwtHandlerInterceptor implements HandlerInterceptor {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private UserInfoService userInfoService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 取出token
String token = request.getHeader("token");
if (!jwtTokenProvider.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"Token已失效,請(qǐng)重新登錄\"}");
return false;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 判斷方法上是否有NoAuth注解,如果有則跳過(guò)認(rèn)證
if (method.isAnnotationPresent(NoAuth.class)) {
return true;
}
String username = jwtTokenProvider.getUsernameFromJWT(token);
User user = userInfoService.getUserInfoByUserName(username);
if (user == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"用戶(hù)不存在\"}");
return false;
}
// 判斷角色權(quán)限
HasRoles hasRoles = handlerMethod.getMethodAnnotation(HasRoles.class);
if (!Objects.isNull(hasRoles)) {
// 檢查用戶(hù)是否有所需角色
String[] roles = hasRoles.value();
boolean hasRole = false;
// 角色校驗(yàn) ...
if (!hasRole) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("{\"error\": \"權(quán)限不足\"}");
return false;
}
}
// 將用戶(hù)信息放入請(qǐng)求屬性
request.setAttribute("currentUser", user);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}在WebMvcConfig配置類(lèi)中,將JwtHandlerInterceptor注冊(cè)到Spring的攔截器鏈中,并設(shè)置攔截路徑為所有請(qǐng)求,排除了登錄和注冊(cè)接口,因?yàn)檫@兩個(gè)接口不需要用戶(hù)登錄就可以訪(fǎng)問(wèn)。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtHandlerInterceptor())
.addPathPatterns("/**")// 攔截所有請(qǐng)求
.excludePathPatterns("/login","/register");// 排除登錄和注冊(cè)接口
}
}通過(guò)這樣的配置,就可以實(shí)現(xiàn)對(duì)用戶(hù)登錄狀態(tài)的驗(yàn)證,確保只有登錄用戶(hù)才能訪(fǎng)問(wèn)受保護(hù)的資源。
2.日志記錄攔截器
日志記錄對(duì)于系統(tǒng)的運(yùn)維和故障排查非常重要。日志記錄攔截器可以在請(qǐng)求處理的前后記錄請(qǐng)求的相關(guān)信息,比如請(qǐng)求的 URL、請(qǐng)求方法、請(qǐng)求參數(shù)、客戶(hù)端 IP 等。這些日志信息可以幫助我們了解系統(tǒng)的運(yùn)行情況,分析用戶(hù)行為,在出現(xiàn)問(wèn)題時(shí)快速定位問(wèn)題。
@Component
public class RequestLoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(RequestLoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("Request URL: {}, Method: {}, IP: {}", request.getRequestURI(), request.getMethod(), request.getRemoteAddr());
// 記錄請(qǐng)求參數(shù)
Map<String, String[]> paramMap = request.getParameterMap();
StringBuilder params = new StringBuilder();
if (!paramMap.isEmpty()) {
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
params.append(entry.getKey())
.append("=")
.append(String.join(",", entry.getValue()))
.append("&");
}
if (params.length() > 0) {
params.deleteCharAt(params.length() - 1);
}
}
// 記錄請(qǐng)求體(僅POST/PUT/PATCH請(qǐng)求)
String method = request.getMethod();
String requestBody = "";
if (HttpMethod.POST.matches(method) ||
HttpMethod.PUT.matches(method) ||
HttpMethod.PATCH.matches(method)) {
// 使用包裝請(qǐng)求對(duì)象來(lái)多次讀取請(qǐng)求體
ContentCachingRequestWrapper wrappedRequest =
new ContentCachingRequestWrapper(request);
// 為了觸發(fā)內(nèi)容緩存,我們需要獲取一次輸入流
if (wrappedRequest.getContentLength() > 0) {
wrappedRequest.getInputStream().read();
requestBody = new String(wrappedRequest.getContentAsByteArray(),
wrappedRequest.getCharacterEncoding());
}
}
logger.info("Request URL: {}, Method: {}, IP: {},params: {},requestBody: {}", request.getRequestURI(), request.getMethod(), request.getRemoteAddr(), params, requestBody);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info("Request to {} has been completed", request.getRequestURI());
if (ex != null) {
logger.error("An exception occurred during request handling", ex);
}
}
}3.性能監(jiān)控?cái)r截器
性能監(jiān)控對(duì)于優(yōu)化系統(tǒng)性能至關(guān)重要。性能監(jiān)控?cái)r截器可以用來(lái)計(jì)算和記錄請(qǐng)求的處理時(shí)間,通過(guò)分析這些時(shí)間數(shù)據(jù),我們可以找出系統(tǒng)中的性能瓶頸,進(jìn)而進(jìn)行針對(duì)性的優(yōu)化。
@Component
public class PerformanceInterceptor implements HandlerInterceptor {
private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
startTimeThreadLocal.set(startTime);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 計(jì)算請(qǐng)求處理時(shí)間
long endTime = System.currentTimeMillis();
long startTime = startTimeThreadLocal.get();
long executeTime = endTime - startTime;
System.out.println("Request URL: " + request.getRequestURL() + ", Execution Time: " + executeTime + "ms");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
startTimeThreadLocal.remove();
}
}4.接口限流攔截器
在高并發(fā)場(chǎng)景下,接口限流是保護(hù)系統(tǒng)的重要手段 。接口限流攔截器可以限制單位時(shí)間內(nèi)對(duì)某個(gè)接口的訪(fǎng)問(wèn)次數(shù),防止因大量請(qǐng)求導(dǎo)致系統(tǒng)資源耗盡,從而保證系統(tǒng)的穩(wěn)定性和可用性 。
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, Integer> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
if (accessLimit == null) {
return true;
}
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
boolean needLogin = accessLimit.needLogin();
if (needLogin) {
// 檢查用戶(hù)登錄狀態(tài),這里省略具體實(shí)現(xiàn)
// 如果未登錄,返回錯(cuò)誤信息
return false;
}
String key = request.getRemoteAddr() + request.getRequestURI();
Integer count = redisTemplate.opsForValue().get(key);
if (count == null) {
redisTemplate.opsForValue().set(key, 1, seconds, TimeUnit.SECONDS);
} else if (count < maxCount) {
redisTemplate.opsForValue().increment(key, 1);
} else {
render(response, "請(qǐng)求過(guò)于頻繁,請(qǐng)稍后再試");
return false;
}
return true;
}
private void render(HttpServletResponse response, String msg) throws IOException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write(msg);
out.flush();
out.close();
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}5.數(shù)據(jù)加密解密攔截器
在數(shù)據(jù)傳輸和存儲(chǔ)過(guò)程中,保護(hù)敏感數(shù)據(jù)的安全至關(guān)重要。數(shù)據(jù)加密解密攔截器可以在請(qǐng)求到達(dá)Controller之前對(duì)敏感數(shù)據(jù)進(jìn)行加密,在響應(yīng)返回給客戶(hù)端之前對(duì)加密數(shù)據(jù)進(jìn)行解密,確保數(shù)據(jù)在傳輸和處理過(guò)程中的安全性。
比如在用戶(hù)登錄時(shí),對(duì)用戶(hù)輸入的密碼進(jìn)行加密后再傳輸?shù)椒?wù)器。在從數(shù)據(jù)庫(kù)中查詢(xún)用戶(hù)的身份證號(hào)、手機(jī)號(hào)等敏感信息時(shí),對(duì)查詢(xún)結(jié)果進(jìn)行解密后再返回給前端。
@Component
public class EncryptionInterceptor implements HandlerInterceptor {
/**
* 密鑰
*/
private static final String KEY = "secret_key";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 假設(shè)請(qǐng)求參數(shù)中有一個(gè)名為"sensitiveData"的敏感數(shù)據(jù)需要加密
String sensitiveData = request.getParameter("sensitiveData");
if (sensitiveData != null) {
SecretKey secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
byte[] encryptedData = AESUtil.encrypt(sensitiveData, secretKey);
String encryptedDataStr = Base64.getEncoder().encodeToString(encryptedData);
// 將加密后的數(shù)據(jù)重新放回請(qǐng)求參數(shù)中
request.setAttribute("sensitiveData", encryptedDataStr);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 假設(shè)響應(yīng)中有一個(gè)名為"sensitiveResponseData"的敏感數(shù)據(jù)需要解密
String sensitiveResponseData = (String) request.getAttribute("sensitiveResponseData");
if (sensitiveResponseData != null) {
SecretKey secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
byte[] decodedData = Base64.getDecoder().decode(sensitiveResponseData);
String decryptedData = AESUtil.decrypt(decodedData, secretKey);
// 將解密后的數(shù)據(jù)重新放回請(qǐng)求屬性中,方便后續(xù)處理
request.setAttribute("sensitiveResponseData", decryptedData);
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
}五、總結(jié)
在 Spring Boot 開(kāi)發(fā)中,攔截器就像是一把 “萬(wàn)能鑰匙”,為我們提供了豐富的功能擴(kuò)展和業(yè)務(wù)處理的可能性。
希望大家在實(shí)際項(xiàng)目中,能夠根據(jù)業(yè)務(wù)需求,靈活地運(yùn)用這些攔截器,讓我們的 Spring Boot 應(yīng)用更加健壯、高效、安全。
同時(shí),攔截器還有很多值得深入探討的地方,比如如何在攔截器中優(yōu)雅地處理事務(wù)、如何實(shí)現(xiàn)更加復(fù)雜的動(dòng)態(tài)攔截規(guī)則等 ,后續(xù)我們可以一起繼續(xù)探索。
如果大家在使用攔截器的過(guò)程中有任何問(wèn)題或心得,歡迎在留言區(qū)分享交流。
到此這篇關(guān)于Spring Boot 攔截器幫助解鎖5大實(shí)用場(chǎng)景(推薦)的文章就介紹到這了,更多相關(guān)springboot攔截器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java聊天室之實(shí)現(xiàn)獲取Socket功能
這篇文章主要為大家詳細(xì)介紹了Java簡(jiǎn)易聊天室之實(shí)現(xiàn)獲取遠(yuǎn)程服務(wù)器和客戶(hù)機(jī)的IP地址和端口號(hào)功能,文中的示例代碼講解詳細(xì),需要的可以了解一下2022-10-10
Component-Scan 不掃描jar里面的類(lèi)問(wèn)題
這篇文章主要介紹了Component-Scan 不掃描jar里面的類(lèi)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Java中volatile關(guān)鍵字的作用與用法詳解
volatile關(guān)鍵字雖然從字面上理解起來(lái)比較簡(jiǎn)單,但是要用好不是一件容易的事情。這篇文章主要介紹了Java中volatile關(guān)鍵字的作用與用法詳解的相關(guān)資料,需要的朋友可以參考下2016-09-09
Java反射機(jī)制如何解決數(shù)據(jù)傳值為空的問(wèn)題
這篇文章主要介紹了Java反射機(jī)制如何解決數(shù)據(jù)傳值為空的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java 集合框架之List 的使用(附小游戲練習(xí))
這篇文章主要介紹Java 集合框架中List 的使用,下面文章將圍繞Java 集合框架中List 的使用展開(kāi)話(huà)題,并附上一些小游戲練習(xí),需要的朋友可以參考一下2021-10-10
Spring?Boot?詳細(xì)分析Conditional自動(dòng)化配置注解
首先我們先了解一下@Conditional注解,@Conditional是Spring4新提供的注解,它的作用是按照一定的條件進(jìn)行判斷,需要注入的Bean滿(mǎn)足給定條件才可以注入到Spring?IOC容器中2022-07-07
Springboot項(xiàng)目啟動(dòng)失敗提示找不到dao類(lèi)的解決
Spring Boot啟動(dòng)失敗,因ProductServiceImpl未正確注入ProductDao,原因:Dao未注冊(cè)為Bean,解決:在啟動(dòng)類(lèi)添加@MapperScan掃描Dao包,或單個(gè)Dao加@Mapper注解,推薦前者以批量注入2025-08-08

