
在 Spring Boot 中,攔截器和動態(tài)代理都是用來實現(xiàn)功能增強(qiáng)的,所以在很多時候,有人會認(rèn)為攔截器的底層是通過動態(tài)代理實現(xiàn)的,所以本文就來盤點一下他們兩的區(qū)別,以及攔截器的底層實現(xiàn)。
1、攔截器
攔截器(Interceptor)準(zhǔn)確來說在 Spring MVC 中的一個很重要的組件,用于攔截 Controller 的請求。它的主要作用有以下幾個:
- 權(quán)限驗證:驗證用戶是否登錄、是否有權(quán)限訪問某個接口。
- 日志記錄:記錄請求信息的日志,如請求參數(shù),響應(yīng)信息等。
- 性能監(jiān)控:監(jiān)控系統(tǒng)的運行性能,如慢查詢接口等。
- 通用行為:插入一些通用的行為,比如開發(fā)環(huán)境忽略某些請求。
典型的使用場景是身份認(rèn)證、授權(quán)檢查、請求日志記錄等。
(1)攔截器實現(xiàn)
在 Spring Boot 中攔截器的實現(xiàn)分為兩步:
- 創(chuàng)建一個普通的攔截器,實現(xiàn) HandlerInterceptor 接口,并重寫接口中的相關(guān)方法。
- 將上一步創(chuàng)建的攔截器加入到 Spring Boot 的配置文件中,并配置攔截規(guī)則。
具體實現(xiàn)如下。
實現(xiàn)自定義攔截器
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import JAVAx.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("攔截器:執(zhí)行 preHandle 方法。");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("攔截器:執(zhí)行 postHandle 方法。");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("攔截器:執(zhí)行 afterCompletion 方法。");
}
}
其中:
- boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle):在請求方法執(zhí)行前被調(diào)用,也就是調(diào)用目標(biāo)方法之前被調(diào)用。比如我們在操作數(shù)據(jù)之前先要驗證用戶的登錄信息,就可以在此方法中實現(xiàn),如果驗證成功則返回 true,繼續(xù)執(zhí)行數(shù)據(jù)操作業(yè)務(wù);否則就返回 false,后續(xù)操作數(shù)據(jù)的業(yè)務(wù)就不會被執(zhí)行了。
- void postHandle(HttpServletRequest request, HttpServletResponse response, Object handle,ModelAndView modelAndView):調(diào)用請求方法之后執(zhí)行,但它會在 DispatcherServlet 進(jìn)行渲染視圖之前被執(zhí)行。
- void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex):會在整個請求結(jié)束之后再執(zhí)行,也就是在 DispatcherServlet 渲染了對應(yīng)的視圖之后再執(zhí)行。
配置攔截規(guī)則
然后,我們再將上面的攔截器注入到項目配置文件中,并設(shè)置相應(yīng)攔截規(guī)則,具體實現(xiàn)代碼如下:
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;
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 注入攔截器
@Autowired
private TestInterceptor testInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(testInterceptor) // 添加攔截器
.addPathPatterns("/**"); // 攔截所有地址
.excludePathPatterns("/login"); // 放行接口
}
}
這樣我們的攔截器就實現(xiàn)完了。
(2)攔截器實現(xiàn)原理
Spring Boot 攔截器是基于 Java 的 Servlet 規(guī)范實現(xiàn)的,通過實現(xiàn) HandlerInterceptor 接口來實現(xiàn)攔截器功能。
在 Spring Boot 框架的執(zhí)行流程中,攔截器被注冊在 DispatcherServlet 的 doDispatch() 方法中,該方法是 Spring Boot 框架的核心方法,用于處理請求和響應(yīng)。
程序每次執(zhí)行時都會調(diào)用 doDispatch() 方法時,并驗證攔截器(鏈),之后再根據(jù)攔截器返回的結(jié)果,進(jìn)行下一步的處理。如果返回的是 true,那么繼續(xù)調(diào)用目標(biāo)方法,反之則會直接返回驗證失敗給前端。
doDispatch 源碼實現(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 {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
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;
}
}
// 調(diào)用預(yù)處理【重點】
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 執(zhí)行 Controller 中的業(yè)務(wù)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
從上述源碼可以看出在開始執(zhí)行 Controller 之前,會先調(diào)用 預(yù)處理方法 applyPreHandle,而 applyPreHandle 方法的實現(xiàn)源碼如下:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
// 獲取項目中使用的攔截器 HandlerInterceptor
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
return true;
}
從上述源碼可以看出,在 applyPreHandle 中會獲取所有的攔截器 HandlerInterceptor 并執(zhí)行攔截器中的 preHandle 方法,這樣就會咱們前面定義的攔截器對應(yīng)上了,如下圖所示:

此時用戶登錄權(quán)限的驗證方法就會執(zhí)行,這就是攔截器的執(zhí)行過程。因此,可以得出結(jié)論,攔截器的實現(xiàn)主要是依賴 Servlet 或 Spring 執(zhí)行流程來進(jìn)行攔截和功能增強(qiáng)的。
2、動態(tài)代理
動態(tài)代理是一種設(shè)計模式,它是指在運行時提供代理對象,來擴(kuò)展目標(biāo)對象的功能。在 Spring 中的,動態(tài)代理的實現(xiàn)手段有以下兩種:
- JDK 動態(tài)代理:通過反射機(jī)制生成代理對象,目標(biāo)對象必須實現(xiàn)接口。
- CGLIB 動態(tài)代理:通過生成目標(biāo)類的子類來實現(xiàn)代理,不要求目標(biāo)對象實現(xiàn)接口。
動態(tài)代理的主要作用包括:
- 擴(kuò)展目標(biāo)對象的功能:如添加日志、驗證參數(shù)等。
- 控制目標(biāo)對象的訪問:如進(jìn)行權(quán)限控制。
- 延遲加載目標(biāo)對象:在需要時才實例化目標(biāo)對象。
- 遠(yuǎn)程代理:將請求轉(zhuǎn)發(fā)到遠(yuǎn)程的目標(biāo)對象上。
“
JDK 動態(tài)代理和 CGLIB 的區(qū)別詳見:www.javacn.site/interview/spring/jdk_cglib.html
”
3、攔截器 VS 動態(tài)代理
因此,我們可以得出結(jié)論,攔截器和動態(tài)代理雖然都是用來實現(xiàn)功能增強(qiáng)的,但二者完全不同,他們的主要區(qū)別體現(xiàn)在以下幾點:
- 使用范圍不同:攔截器通常用于 Spring MVC 中,主要用于攔截 Controller 請求。動態(tài)代理可以使用在 Bean 中,主要用于提供 bean 的代理對象,實現(xiàn)對 bean 方法的攔截。
- 實現(xiàn)原理不同:攔截器是通過 HandlerInterceptor 接口來實現(xiàn)的,主要是通過 afterCompletion、postHandle、preHandle 這三個方法在請求前后進(jìn)行攔截處理。動態(tài)代理主要有 JDK 動態(tài)代理和 CGLIB 動態(tài)代理,JDK 通過反射生成代理類;CGLIB 通過生成被代理類的子類來實現(xiàn)代理。
- 加入時機(jī)不同:攔截器是在運行階段動態(tài)加入的;動態(tài)代理是在編譯期或運行期生成的代理類。
- 使用難易程度不同:攔截器相對簡單,通過實現(xiàn)接口即可使用。動態(tài)代理稍微復(fù)雜,需要了解動態(tài)代理的實現(xiàn)原理,然后通過相應(yīng)的 api 實現(xiàn)。
小結(jié)
在 Spring Boot 中,攔截器和動態(tài)代理都是用來實現(xiàn)功能增強(qiáng)的,但二者沒有任何關(guān)聯(lián)關(guān)系,它的區(qū)別主要體現(xiàn)在使用范圍、實現(xiàn)原理、加入時機(jī)和使用的難易程度都是不同的。






