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

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

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

后端日志:

此時(shí)看到只有“目標(biāo)方法執(zhí)行前執(zhí)行”這一條日志。
原因是因?yàn)椋簆reHandle方法攔截了所有請(qǐng)求。
攔截器詳解
攔截器路徑
攔截路徑是指我們定義的這個(gè)攔截器, 對(duì)哪些請(qǐng)求?效.
我們?cè)谧?cè)配置攔截器的時(shí)候, 通過(guò) addPathPatterns() ?法指定要攔截哪些請(qǐng)求. 也可以通過(guò)
excludePathPatterns() 指定不攔截哪些請(qǐng)求.
上述代碼中, 我們配置的是 /** , 表?攔截所有的請(qǐng)求.
?如用戶登錄校驗(yàn), 我們希望可以對(duì)除了登錄之外所有的路徑?效.
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")
;
}
}在攔截器中除了可以設(shè)置 /** 攔截所有資源外,還有一些常見(jiàn)攔截路徑設(shè)置:

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

有了攔截器之后,會(huì)在調(diào)? Controller 之前進(jìn)?相應(yīng)的業(yè)務(wù)處理,執(zhí)?的流程如下圖:

1. 添加攔截器后, 執(zhí)?Controller的?法之前, 請(qǐng)求會(huì)先被攔截器攔截住. 執(zhí)? preHandle() ?法,這個(gè)?法需要返回?個(gè)布爾類型的值. 如果返回true, 就表?放?本次操作, 繼續(xù)訪問(wèn)controller中的?法. 如果返回false,則不會(huì)放?(controller中的?法也不會(huì)執(zhí)?).
2. controller當(dāng)中的?法執(zhí)?完畢后,再回過(guò)來(lái)執(zhí)? postHandle() 這個(gè)?法以及
afterCompletion() ?法,執(zhí)?完畢之后,最終給瀏覽器響應(yīng)數(shù)據(jù).
修改之前登錄校驗(yàn)功能
定義攔截器
從session中獲取??信息, 如果session中不存在, 則返回false,并設(shè)置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中存儲(chǔ)的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;
}
}注冊(cè)配置攔截器
@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")
;
}
}使用瀏覽器訪問(wèn)
http://127.0.0.1:8080/book/queryBookById?bookId=1
訪問(wèn)結(jié)果:

使用Fiddler抓包:

登陸之后再訪問(wèn)上面的鏈接

DispatcherServlet源碼解析(重要)
觀察服務(wù)啟動(dòng)日志:

當(dāng)Tomcat啟動(dòng)之后, 有?個(gè)核?的類DispatcherServlet, 它來(lái)控制程序的執(zhí)?順序.
所有請(qǐng)求都會(huì)先進(jìn)到DispatcherServlet,執(zhí)?doDispatch 調(diào)度?法. 如果有攔截器, 會(huì)先執(zhí)?攔截器preHandle() ?法的代碼, 如果 preHandle() 返回true, 繼續(xù)訪問(wèn)controller中的?法. controller
當(dāng)中的?法執(zhí)?完畢后,再回過(guò)來(lái)執(zhí)? postHandle() 和 afterCompletion() ,返回給
DispatcherServlet, 最終給瀏覽器響應(yīng)數(shù)據(jù).

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

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

此處打印的?志, 也正是控制臺(tái)打印出來(lái)的?志:

初始化web容器的過(guò)程中, 會(huì)通過(guò)onRefresh 來(lái)初始化SpringMVC的容器:

在initStrategies()中進(jìn)?9?組件的初始化, 如果沒(méi)有配置相應(yīng)的組件,就使?默認(rèn)定義的組件(在
DispatcherServlet.properties中有配置默認(rèn)的策略)。
關(guān)于9大組件的解釋
1. 初始化?件上傳解析器MultipartResolver:從應(yīng)?上下?中獲取名稱為multipartResolver的Bean,如果沒(méi)有名為multipartResolver的Bean,則沒(méi)有提供上傳?件的解析器;
2. 初始化區(qū)域解析器LocaleResolver:從應(yīng)?上下?中獲取名稱為localeResolver的Bean,如果沒(méi)有這個(gè)Bean,則默認(rèn)使?AcceptHeaderLocaleResolver作為區(qū)域解析器;
3. 初始化主題解析器ThemeResolver:從應(yīng)?上下?中獲取名稱為themeResolver的Bean,如果沒(méi)有這個(gè)Bean,則默認(rèn)使?FixedThemeResolver作為主題解析器;
4. 初始化處理器映射器HandlerMappings:處理器映射器作?,1)通過(guò)處理器映射器找到對(duì)應(yīng)的處理器適配器,將請(qǐng)求交給適配器處理;2)緩存每個(gè)請(qǐng)求地址URL對(duì)應(yīng)的位置(Controller.xxx?法);如果在ApplicationContext發(fā)現(xiàn)有HandlerMappings,則從ApplicationContext中獲取到所有的HandlerMappings,并進(jìn)?排序;如果在ApplicationContext中沒(méi)有發(fā)現(xiàn)有處理器映射器,則默認(rèn)BeanNameUrlHandlerMapping作為處理器映射器;
5. 初始化處理器適配器HandlerAdapter:作?是通過(guò)調(diào)?具體的?法來(lái)處理具體的請(qǐng)求;如果在ApplicationContext發(fā)現(xiàn)有handlerAdapter,則從ApplicationContext中獲取到所有的
HandlerAdapter,并進(jìn)?排序;如果在ApplicationContext中沒(méi)有發(fā)現(xiàn)處理器適配器,則默認(rèn)
SimpleControllerHandlerAdapter作為處理器適配器;
6. 初始化異常處理器解析器HandlerExceptionResolver:如果在ApplicationContext發(fā)現(xiàn)有
handlerExceptionResolver,則從ApplicationContext中獲取到所有的
HandlerExceptionResolver,并進(jìn)?排序;如果在ApplicationContext中沒(méi)有發(fā)現(xiàn)異常處理器解析器,則不設(shè)置異常處理器;
7. 初始化RequestToViewNameTranslator:其作?是從Request中獲取viewName,從
ApplicationContext發(fā)現(xiàn)有viewNameTranslator的Bean,如果沒(méi)有,則默認(rèn)使?
DefaultRequestToViewNameTranslator;
8. 初始化視圖解析器ViewResolvers:先從ApplicationContext中獲取名為viewResolver的Bean,如果沒(méi)有,則默認(rèn)InternalResourceViewResolver作為視圖解析器;
9. 初始化FlashMapManager:其作?是?于檢索和保存FlashMap(保存從?個(gè)URL重定向到另?個(gè)URL時(shí)的參數(shù)信息),從ApplicationContext發(fā)現(xiàn)有flashMapManager的Bean,如果沒(méi)有,則默認(rèn)使?DefaultFlashMapManager 。
處理請(qǐng)求(核心)
DispatcherServlet 接收到請(qǐng)求后, 執(zhí)?doDispatch 調(diào)度?法, 再將請(qǐng)求轉(zhuǎn)給Controller.
我們來(lái)看doDispatch ?法的具體實(shí)現(xiàn):
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);
}
}
}
}其中有三個(gè)方法關(guān)注一下:


上面的三個(gè)方法對(duì)應(yīng)我們之前使用到的HandlerInterceptor接口:

適配器模式
HandlerAdapter 在 Spring MVC 中使?了適配器模式
適配器模式定義
適配器模式, 也叫包裝器模式. 將?個(gè)類的接?,轉(zhuǎn)換成客?期望的另?個(gè)接?, 適配器讓原本接?不兼容的類可以合作?間.
簡(jiǎn)單來(lái)說(shuō)就是?標(biāo)類不能直接使?, 通過(guò)?個(gè)新類進(jìn)?包裝?下, 適配調(diào)??使?. 把兩個(gè)不兼容的接?通過(guò)?定的?式使之兼容.
?如下?兩個(gè)接?, 本?是不兼容的(參數(shù)類型不?樣, 參數(shù)個(gè)數(shù)不?樣等等):

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

適配器模式角色
• Target: ?標(biāo)接? (可以是抽象類或接?), 客?希望直接?的接?
• Adaptee: 適配者, 但是與Target不兼容
• Adapter: 適配器類, 此模式的核?. 通過(guò)繼承或者引?適配者的對(duì)象, 把適配者轉(zhuǎn)為?標(biāo)接?
• client: 需要使?適配器的對(duì)象
適配器模式的實(shí)現(xiàn)
場(chǎng)景: 前?學(xué)習(xí)的slf4j 就使?了適配器模式, slf4j提供了?系列打印?志的api, 底層調(diào)?的是log4j 或者logback來(lái)打?志, 我們作為調(diào)?者, 只需要調(diào)?slf4j的api就?了.
Log4j
public class Log4j {
public void log4jPrint(String message){
System.out.println("我是Log4j, 打印日志內(nèi)容為: "+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("我是客戶端");
}
}運(yùn)行結(jié)果:

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

