SpringBoot中的攔截器使用詳解
攔截器
之前我們完成的圖書管理系統(tǒng)完成了強制登錄的功能, 實現原理是后端在用戶登錄成功后會把用戶信息存儲到了Session中,后端程序根據Session來判斷用戶是否登錄, 但是這樣是?較麻煩的:
• 需要修改每個接?的處理邏輯
• 需要修改每個接?的返回結果
• 接?定義修改, 前端代碼也需要跟著修改
有沒有更簡單的辦法, 統(tǒng)?攔截所有的請求, 并進?Session校驗呢?
這?我們學習?種新的解決辦法: 攔截器。
攔截器是Spring框架提供的核?功能之?, 主要?來攔截??的請求, 在指定?法前后, 根據業(yè)務需要執(zhí)行預先設定的代碼。

攔截器的使用
攔截器的使用分為兩步:
1.定義攔截器;
2.注冊配置攔截器。
自定義攔截器
實現HandlerInterceptor接口,并重寫其所有方法
LoginInterceptor
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("LoginInterceptor 目標方法執(zhí)行前執(zhí)行");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor 目標方法執(zhí)行后執(zhí)行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor 視圖渲染完畢后執(zhí)行,最后執(zhí)行");
}
}• preHandle()?法:?標?法執(zhí)?前執(zhí)?. 返回true表示可以繼續(xù)執(zhí)?后續(xù)操作; 返回false表示中斷后續(xù)操作。
• postHandle()?法:?標?法執(zhí)?后執(zhí)?。
• afterCompletion()?法:視圖渲染完畢后執(zhí)?,最后執(zhí)? 。
注冊配置攔截器
實現WebMvcConfigurer接口,并重寫addInterceptors方法
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
;
}
}啟動服務,登陸成功后,發(fā)送根據圖書id查詢圖書請求,觀察后端日志:

可以看到preHandle方法執(zhí)行之后就放行了,開始執(zhí)行目標方法,目標方法執(zhí)行完成之后執(zhí)行postHandle和afterCompletion方法。
我們吧攔截器中preHandle方法的返回值改為false,在觀察后端日志:

后端日志:

此時看到只有“目標方法執(zhí)行前執(zhí)行”這一條日志。
原因是因為:preHandle方法攔截了所有請求。
攔截器詳解
攔截器路徑
攔截路徑是指我們定義的這個攔截器, 對哪些請求?效.
我們在注冊配置攔截器的時候, 通過 addPathPatterns() ?法指定要攔截哪些請求. 也可以通過
excludePathPatterns() 指定不攔截哪些請求.
上述代碼中, 我們配置的是 /** , 表?攔截所有的請求.
?如用戶登錄校驗, 我們希望可以對除了登錄之外所有的路徑?效.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springbook.interceptor.LoginInterceptor;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
;
}
}在攔截器中除了可以設置 /** 攔截所有資源外,還有一些常見攔截路徑設置:

攔截器執(zhí)行流程
正常的調用順序:

有了攔截器之后,會在調? Controller 之前進?相應的業(yè)務處理,執(zhí)?的流程如下圖:

1. 添加攔截器后, 執(zhí)?Controller的?法之前, 請求會先被攔截器攔截住. 執(zhí)? preHandle() ?法,這個?法需要返回?個布爾類型的值. 如果返回true, 就表?放?本次操作, 繼續(xù)訪問controller中的?法. 如果返回false,則不會放?(controller中的?法也不會執(zhí)?).
2. controller當中的?法執(zhí)?完畢后,再回過來執(zhí)? postHandle() 這個?法以及
afterCompletion() ?法,執(zhí)?完畢之后,最終給瀏覽器響應數據.
修改之前登錄校驗功能
定義攔截器
從session中獲取??信息, 如果session中不存在, 則返回false,并設置http狀態(tài)碼為401, 否則返回true.
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//true 表示放行 false表示攔截
log.info("LoginInterceptor preHandle....");
//獲取session, 并且判斷session中存儲的userinfo信息是否為空
HttpSession session = request.getSession();
UserInfo userInfo = (UserInfo) session.getAttribute(Constants.USER_SESSION_KEY);
if (userInfo==null || userInfo.getId()<=0){
//用戶未登錄
response.setStatus(401);
return false;
}
return true;
}
}注冊配置攔截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/css/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/pic/**")
.excludePathPatterns("/**/*.html")
;
}
}使用瀏覽器訪問
http://127.0.0.1:8080/book/queryBookById?bookId=1
訪問結果:

使用Fiddler抓包:

登陸之后再訪問上面的鏈接

DispatcherServlet源碼解析(重要)
觀察服務啟動日志:

當Tomcat啟動之后, 有?個核?的類DispatcherServlet, 它來控制程序的執(zhí)?順序.
所有請求都會先進到DispatcherServlet,執(zhí)?doDispatch 調度?法. 如果有攔截器, 會先執(zhí)?攔截器preHandle() ?法的代碼, 如果 preHandle() 返回true, 繼續(xù)訪問controller中的?法. controller
當中的?法執(zhí)?完畢后,再回過來執(zhí)? postHandle() 和 afterCompletion() ,返回給
DispatcherServlet, 最終給瀏覽器響應數據.

初始化
DispatcherServlet的初始化?法 init() 在其?類 HttpServletBean 中實現的.
主要作?是加載 web.xml 中 DispatcherServlet 的 配置, 并調??類的初始化.

在 HttpServletBean 的 init() 中調?了 initServletBean() , 它是在
FrameworkServlet 類中實現的, 主要作?是建? WebApplicationContext 容器(有時也稱上下?), 并加載 SpringMVC 配置?件中定義的 Bean到該容器中, 最后將該容器添加到 ServletContext 中. 下?是initServletBean() 的具體代碼:

此處打印的?志, 也正是控制臺打印出來的?志:

初始化web容器的過程中, 會通過onRefresh 來初始化SpringMVC的容器:

在initStrategies()中進?9?組件的初始化, 如果沒有配置相應的組件,就使?默認定義的組件(在
DispatcherServlet.properties中有配置默認的策略)。
關于9大組件的解釋
1. 初始化?件上傳解析器MultipartResolver:從應?上下?中獲取名稱為multipartResolver的Bean,如果沒有名為multipartResolver的Bean,則沒有提供上傳?件的解析器;
2. 初始化區(qū)域解析器LocaleResolver:從應?上下?中獲取名稱為localeResolver的Bean,如果沒有這個Bean,則默認使?AcceptHeaderLocaleResolver作為區(qū)域解析器;
3. 初始化主題解析器ThemeResolver:從應?上下?中獲取名稱為themeResolver的Bean,如果沒有這個Bean,則默認使?FixedThemeResolver作為主題解析器;
4. 初始化處理器映射器HandlerMappings:處理器映射器作?,1)通過處理器映射器找到對應的處理器適配器,將請求交給適配器處理;2)緩存每個請求地址URL對應的位置(Controller.xxx?法);如果在ApplicationContext發(fā)現有HandlerMappings,則從ApplicationContext中獲取到所有的HandlerMappings,并進?排序;如果在ApplicationContext中沒有發(fā)現有處理器映射器,則默認BeanNameUrlHandlerMapping作為處理器映射器;
5. 初始化處理器適配器HandlerAdapter:作?是通過調?具體的?法來處理具體的請求;如果在ApplicationContext發(fā)現有handlerAdapter,則從ApplicationContext中獲取到所有的
HandlerAdapter,并進?排序;如果在ApplicationContext中沒有發(fā)現處理器適配器,則默認
SimpleControllerHandlerAdapter作為處理器適配器;
6. 初始化異常處理器解析器HandlerExceptionResolver:如果在ApplicationContext發(fā)現有
handlerExceptionResolver,則從ApplicationContext中獲取到所有的
HandlerExceptionResolver,并進?排序;如果在ApplicationContext中沒有發(fā)現異常處理器解析器,則不設置異常處理器;
7. 初始化RequestToViewNameTranslator:其作?是從Request中獲取viewName,從
ApplicationContext發(fā)現有viewNameTranslator的Bean,如果沒有,則默認使?
DefaultRequestToViewNameTranslator;
8. 初始化視圖解析器ViewResolvers:先從ApplicationContext中獲取名為viewResolver的Bean,如果沒有,則默認InternalResourceViewResolver作為視圖解析器;
9. 初始化FlashMapManager:其作?是?于檢索和保存FlashMap(保存從?個URL重定向到另?個URL時的參數信息),從ApplicationContext發(fā)現有flashMapManager的Bean,如果沒有,則默認使?DefaultFlashMapManager 。
處理請求(核心)
DispatcherServlet 接收到請求后, 執(zhí)?doDispatch 調度?法, 再將請求轉給Controller.
我們來看doDispatch ?法的具體實現:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new ServletException("Handler dispatch failed: " + err, err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new ServletException("Handler processing failed: " + err, err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}其中有三個方法關注一下:


上面的三個方法對應我們之前使用到的HandlerInterceptor接口:

適配器模式
HandlerAdapter 在 Spring MVC 中使?了適配器模式
適配器模式定義
適配器模式, 也叫包裝器模式. 將?個類的接?,轉換成客?期望的另?個接?, 適配器讓原本接?不兼容的類可以合作?間.
簡單來說就是?標類不能直接使?, 通過?個新類進?包裝?下, 適配調??使?. 把兩個不兼容的接?通過?定的?式使之兼容.
?如下?兩個接?, 本?是不兼容的(參數類型不?樣, 參數個數不?樣等等):

可以通過適配器的?式, 使之兼容:

適配器模式角色
• Target: ?標接? (可以是抽象類或接?), 客?希望直接?的接?
• Adaptee: 適配者, 但是與Target不兼容
• Adapter: 適配器類, 此模式的核?. 通過繼承或者引?適配者的對象, 把適配者轉為?標接?
• client: 需要使?適配器的對象
適配器模式的實現
場景: 前?學習的slf4j 就使?了適配器模式, slf4j提供了?系列打印?志的api, 底層調?的是log4j 或者logback來打?志, 我們作為調?者, 只需要調?slf4j的api就?了.
Log4j
public class Log4j {
public void log4jPrint(String message){
System.out.println("我是Log4j, 打印日志內容為: "+message);
}
}Log4jAdapter
public class Log4jAdapter implements Slf4jLog{
private Log4j log4j;
public Log4jAdapter(Log4j log4j) {
this.log4j = log4j;
}
@Override
public void log(String message) {
log4j.log4jPrint("我是是適配器, 打印日志為: "+ message);
}
}Slf4jLog
public interface Slf4jLog {
void log(String message);
}Main
public class Main {
public static void main(String[] args) {
Slf4jLog slf4jLog = new Log4jAdapter(new Log4j());
slf4jLog.log("我是客戶端");
}
}運行結果:

可以看出, 我們不需要改變log4j的api,只需要通過適配器轉換下, 就可以更換?志框架, 保障系統(tǒng)的平穩(wěn)運行.
適配器模式的優(yōu)缺點
優(yōu)點
提高系統(tǒng)的靈活性和可擴展性:通過適配器模式,可以很方便地增加新的適配器來支持新的接口,而不需要修改原有的代碼。
解耦:客戶端通過適配器與需要適配的類進行交互,降低了客戶端與需要適配的類之間的耦合度。
缺點
過多使用適配器會使系統(tǒng)變得復雜:由于引入了適配器,系統(tǒng)的類數量會增加,這可能會使系統(tǒng)的結構變得復雜。
可能隱藏了需要適配的類的真正功能:客戶端通過適配器與需要適配的類進行交互,可能會忽略需要適配的類的某些功能。
適配器模式的應用場景
當系統(tǒng)需要使用現有的類,而這些類的接口不符合系統(tǒng)的需要時。
想要建立一個可以重復使用的類庫,以便與一些彼此之間沒有太大關聯(lián)甚至完全不兼容的類一起工作。
在開發(fā)過程中,由于某些原因(如框架升級、第三方庫變更等)導致原有的接口發(fā)生變化,而客戶端代碼依賴于舊的接口時。
到此這篇關于SpringBoot之攔截器的文章就介紹到這了,更多相關SpringBoot攔截器內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- SpringBoot集成MyBatis實現SQL攔截器的實戰(zhàn)指南
- SpringBoot攔截器執(zhí)行后,Controller層不執(zhí)行的問題及解決
- SpringBoot集成MyBatis中SQL攔截器的實戰(zhàn)指南
- springboot攔截器HandlerInterceptor不生效的原因排查
- SpringBoot項目Web攔截器使用的多種方式
- 6種常見的SpringBoot攔截器使用場景及實現方式
- springboot使用Gateway做網關并且配置全局攔截器的方式
- Mybatis 的 Interceptor(攔截器) 與 JSqlparser 結合解析SQL 使SpringBoot項目多數據庫兼容的嘗試(推薦)
- SpringBoot基于攔截器與ThreadLocal實現用戶登錄校驗
相關文章
Caffeine本地緩存核心原理與使用方法詳解(含與Spring?Cache集成)
本文介紹Caffeine作為高性能本地緩存庫,具備智能淘汰策略和并發(fā)優(yōu)化,集成SpringCache實現緩存管理,探討緩存穿透、擊穿、雪崩防護及多級緩存(Caffeine+Redis)方案,適用于Java生態(tài)的緩存實踐,感興趣的朋友跟隨小編一起看看吧2025-09-09
Spring 定時任務@Scheduled 注解中的 Cron 表達式詳解
Cron 表達式是一種用于定義定時任務觸發(fā)時間的字符串表示形式,它由七個字段組成,分別表示秒、分鐘、小時、日期、月份、星期和年份,這篇文章主要介紹了Spring 定時任務@Scheduled 注解中的 Cron 表達式,需要的朋友可以參考下2023-07-07

