過濾器 和 攔截器 6個區別,別再傻傻分不清了
阿新 • • 發佈:2020-06-04
>本文收錄在個人部落格:[www.chengxy-nds.top](http://www.chengxy-nds.top),技術資料共享,同進步
週末有個小夥伴加我微信,向我請教了一個問題:老哥,**過濾器 (`Filter`) 和 攔截器 (`Interceptor`) 有啥區別啊?** 聽到題目我的第一感覺就是:**簡單**!
畢竟這兩種工具開發中用到的頻率都相當高,應用起來也是比較簡單的,可當我準備回覆他的時候,竟然不知道從哪說起,支支吾吾了半天,場面炒雞尷尬有木有,工作這麼久一個基礎問題答成這樣,丟了大人了。
![自導自演,別太當真過,哈哈哈](https://img-blog.csdnimg.cn/20200601163732522.png)
平時覺得簡單的知識點,但通常都不會太關注細節,一旦被別人問起來,反倒說不出個所以然來。
歸根結底,還是對這些知識瞭解的不夠,一直停留在會用的階段,以至於現在**一看就會一說就廢**!這是典型基礎不紮實的表現,哎·~,其實我也就是個虛胖!
知恥而後勇,下邊結合實踐,更直觀的來感受一下兩者到底有什麼不同?
### 準備環境
我們在專案中同時配置 `攔截器` 和 `過濾器`。
#### 1、過濾器 (Filter)
過濾器的配置比較簡單,直接實現`Filter` 介面即可,也可以通過`@WebFilter`註解實現對特定`URL`攔截,看到`Filter` 介面中定義了三個方法。
- `init()` :該方法在容器啟動初始化過濾器時被呼叫,它在 `Filter` 的整個生命週期只會被呼叫一次。**注意**:這個方法必須執行成功,否則過濾器會不起作用。
- `doFilter()` :容器中的每一次請求都會呼叫該方法, `FilterChain` 用來呼叫下一個過濾器 `Filter`。
- `destroy()`: 當容器銷燬 過濾器例項時呼叫該方法,一般在方法中銷燬或關閉資源,在過濾器 `Filter` 的整個生命週期也只會被呼叫一次
```javascript
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter 前置");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter 處理中");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("Filter 後置");
}
}
```
#### 2、攔截器 (Interceptor)
攔截器它是鏈式呼叫,一個應用中可以同時存在多個攔截器`Interceptor`, 一個請求也可以觸發多個攔截器 ,而每個攔截器的呼叫會依據它的宣告順序依次執行。
首先編寫一個簡單的攔截器處理類,請求的攔截是通過`HandlerInterceptor` 來實現,看到`HandlerInterceptor` 介面中也定義了三個方法。
- `preHandle()` :這個方法將在請求處理之前進行呼叫。**注意**:如果該方法的返回值為`false` ,將視為當前請求結束,不僅自身的攔截器會失效,還會導致其他的攔截器也不再執行。
- `postHandle()`:只有在 `preHandle()` 方法返回值為`true` 時才會執行。會在Controller 中的方法呼叫之後,DispatcherServlet 返回渲染檢視之前被呼叫。 **有意思的是**:`postHandle()` 方法被呼叫的順序跟 `preHandle()` 是相反的,先宣告的攔截器 `preHandle()` 方法先執行,而`postHandle()`方法反而會後執行。
- `afterCompletion()`:只有在 `preHandle()` 方法返回值為`true` 時才會執行。在整個請求結束之後, DispatcherServlet 渲染了對應的檢視之後執行。
```javascript
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor 前置");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor 處理中");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor 後置");
}
}
```
將自定義好的攔截器處理類進行註冊,並通過`addPathPatterns`、`excludePathPatterns`等屬性設定需要攔截或需要排除的 `URL`。
```javascript
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
}
}
```
### 我們不一樣
過濾器 和 攔截器 均體現了`AOP`的程式設計思想,都可以實現諸如日誌記錄、登入鑑權等功能,但二者的不同點也是比較多的,接下來一一說明。
#### 1、實現原理不同
過濾器和攔截器 底層實現方式大不相同,`過濾器` 是基於函式回撥的,`攔截器` 則是基於Java的反射機制(動態代理)實現的。
這裡重點說下過濾器!
在我們自定義的過濾器中都會實現一個 `doFilter()`方法,這個方法有一個`FilterChain` 引數,而實際上它是一個回撥介面。`ApplicationFilterChain`是它的實現類, 這個實現類內部也有一個 `doFilter()` 方法就是回撥方法。
```javascript
public interface FilterChain {
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
```
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020060311274589.png?#pic_center)
`ApplicationFilterChain`裡面能拿到我們自定義的`xxxFilter`類,在其內部回撥方法`doFilter()`裡呼叫各個自定義`xxxFilter`過濾器,並執行 `doFilter()` 方法。
```javascript
public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
...//省略
internalDoFilter(request,response);
}
private void internalDoFilter(ServletRequest request, ServletResponse response){
if (pos < n) {
//獲取第pos個filter
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
...
filter.doFilter(request, response, this);
}
}
}
```
而每個`xxxFilter` 會先執行自身的 `doFilter()` 過濾邏輯,最後在執行結束前會執行`filterChain.doFilter(servletRequest, servletResponse)`,也就是回撥`ApplicationFilterChain`的`doFilter()` 方法,以此迴圈執行實現函式回撥。
```javascript
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
```
#### 2、使用範圍不同
我們看到過濾器 實現的是 `javax.servlet.Filter` 介面,而這個介面是在`Servlet`規範中定義的,也就是說過濾器`Filter` 的使用要依賴於`Tomcat`等容器,導致它只能在`web`程式中使用。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200602141320121.png?)
而攔截器(`Interceptor`) 它是一個`Spring`元件,並由`Spring`容器管理,並不依賴`Tomcat`等容器,是可以單獨使用的。不僅能應用在`web`程式中,也可以用於`Application`、`Swing`等程式中。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200602172813157.png)
#### 3、觸發時機不同
`過濾器` 和 `攔截器`的觸發時機也不同,我們看下邊這張圖。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200602173814901.png?#pic_center)
過濾器`Filter`是在請求進入容器後,但在進入`servlet`之前進行預處理,請求結束是在`servlet`處理完以後。
攔截器 `Interceptor` 是在請求進入`servlet`後,在進入`Controller`之前進行預處理的,`Controller` 中渲染了對應的檢視之後請求結束。
#### 4、攔截的請求範圍不同
在上邊我們已經同時配置了過濾器和攔截器,再建一個`Controller`接收請求測試一下。
```javascript
@Controller
@RequestMapping()
public class Test {
@RequestMapping("/test1")
@ResponseBody
public String test1(String a) {
System.out.println("我是controller");
return null;
}
}
```
專案啟動過程中發現,過濾器的`init()`方法,隨著容器的啟動進行了初始化。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200603152935516.png?)
此時瀏覽器傳送請求,F12 看到居然有兩個請求,一個是我們自定義的 `Controller` 請求,另一個是訪問靜態圖示資源的請求。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020060315412216.png)
看到控制檯的列印日誌如下:
執行順序 :`Filter 處理中` -> `Interceptor 前置` -> `我是controller` -> `Interceptor 處理中` -> `Interceptor 處理後`
```javascript
Filter 處理中
Interceptor 前置
Interceptor 處理中
Interceptor 後置
Filter 處理中
```
過濾器`Filter`執行了兩次,攔截器`Interceptor`只執行了一次。這是因為過濾器幾乎可以對所有進入容器的請求起作用,而攔截器只會對`Controller`中請求或訪問`static`目錄下的資源請求起作用。
#### 5、注入Bean情況不同
在實際的業務場景中,應用到過濾器或攔截器,為處理業務邏輯難免會引入一些`service`服務。
下邊我們分別在過濾器和攔截器中都注入`service`,看看有什麼不同?
```javascript
@Component
public class TestServiceImpl implements TestService {
@Override
public void a() {
System.out.println("我是方法A");
}
}
```
過濾器中注入`service`,發起請求測試一下 ,日誌正常打印出`“我是方法A”`。
```javascript
@Autowired
private TestService testService;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter 處理中");
testService.a();
filterChain.doFilter(servletRequest, servletResponse);
}
```
```javascript
Filter 處理中
我是方法A
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor 後置
```
在攔截器中注入`service`,發起請求測試一下 ,竟然TM的報錯了,`debug`跟一下發現注入的`service`怎麼是`Null`啊?
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200603163633360.png?)
這是因為載入順序導致的問題,`攔截器`載入的時間點在`springcontext`之前,而`Bean`又是由`spring`進行管理。
> 攔截器:老子今天要進洞房;
>Spring:兄弟別鬧,你媳婦我還沒生出來呢!
解決方案也很簡單,我們在註冊攔截器之前,先將`Interceptor` 手動進行注入。**注意**:在`registry.addInterceptor()`註冊的是`getMyInterceptor()` 例項。
```javascript
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Bean
public MyInterceptor getMyInterceptor(){
System.out.println("注入了MyInterceptor");
return new MyInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
}
}
```
#### 6、控制執行順序不同
實際開發過程中,會出現多個過濾器或攔截器同時存在的情況,不過,有時我們希望某個過濾器或攔截器能優先執行,就涉及到它們的執行順序。
過濾器用`@Order`註解控制執行順序,通過`@Order`控制過濾器的級別,值越小級別越高越先執行。
```javascript
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {
```
攔截器預設的執行順序,就是它的註冊順序,也可以通過`Order`手動設定控制,值越小越先執行。
```javascript
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
}
```
看到輸出結果發現,先宣告的攔截器 `preHandle()` 方法先執行,而`postHandle()`方法反而會後執行。
`postHandle()` 方法被呼叫的順序跟 `preHandle()` 居然是相反的!如果實際開發中嚴格要求執行順序,那就需要特別注意這一點。
```javascript
Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor2 處理中
Interceptor1 處理中
Interceptor 後置
Interceptor2 處理後
Interceptor1 處理後
```
**那為什麼會這樣呢?** 得到答案就只能看原始碼了,我們要知道`controller` 中所有的請求都要經過核心元件`DispatcherServlet`路由,都會執行它的 `doDispatch()` 方法,而攔截器`postHandle()`、`preHandle()`方法便是在其中呼叫的。
```javascript
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
...........
try {
// 獲取可以執行當前Handler的介面卡
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 注意: 執行Interceptor中PreHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 注意:執行Handle【包括我們的業務邏輯,當丟擲異常時會被Try、catch到】
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 注意:執行Interceptor中PostHandle 方法【丟擲異常時無法執行】
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
...........
}
```
看看兩個方法`applyPreHandle()`、`applyPostHandle()`具體是如何被呼叫的,就明白為什麼`postHandle()`、`preHandle()` 執行順序是相反的了。
```javascript
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
if(!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
return true;
}
```
```javascript
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = interceptors.length - 1; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
```
發現兩個方法中在呼叫攔截器陣列 `HandlerInterceptor[]` 時,迴圈的順序竟然是相反的。。。,導致`postHandle()`、`preHandle()` 方法執行的順序相反。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200603184307199.png?)
### 總結
我相信大部分人都能熟練使用濾器和攔截器,但兩者的差別還是需要多瞭解下,不然開發中使用不當,時不時就會出現奇奇怪怪的問題,以上內容比較簡單,新手學習老鳥複習,有遺漏的地方還望大家積極補充,如有理解錯誤之處,還望不吝賜教。
---
原創不易,**燃燒秀髮輸出內容**
>整理了幾百本各類技術電子書, 送給小夥伴們, 我的同名公眾號自行領取。和一些小夥伴們建了一個技術交流群,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就加入我們吧!
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC8yLzQvMTcwMGU0Mjk1MDQzMjQ0Yg?x-oss-process=image/for