SpringBoot系列教程web篇之過濾器Filter使用指南
web三大元件之一Filter,可以說是很多小夥伴學習java web時最早接觸的知識點了,然而學得早不代表就用得多。基本上,如果不是讓你從0到1寫一個web應用(或者說即便從0到1寫一個web應用),在你的日常業務開發中不太可能碰到需要手寫Filter的場景
本文將簡單介紹寫什麼是Filter,以及在SpringBoot中使用Filter的一般姿勢與常見問題
I. 背景
在正式開始之前,有必要先簡單看一下什麼是Filter(過濾器),以及這個有什麼用
1. Filter說明
Filter,過濾器,屬於Servlet規範,並不是Spring獨有的。其作用從命名上也可以看出一二,攔截一個請求,做一些業務邏輯操作,然後可以決定請求是否可以繼續往下分發,落到其他的Filter或者對應的Servlet
簡單描述下一個http請求過來之後,一個Filter的工作流程:
- 首先進入filter,執行相關業務邏輯
- 若判定通行,則進入Servlet邏輯,Servlet執行完畢之後,又返回Filter,最後在返回給請求方
- 判定失敗,直接返回,不需要將請求發給Servlet
插播一句:上面這個過程,和AOP中的
@Around
環繞切面的作用差不多
2. 專案搭建
接下來我們搭建一個web應用方便後續的演示,藉助SpringBoot搭建一個web應用屬於比較簡單的活;
建立一個maven專案,pom檔案如下
<parent>
<groupId>org.springframework.boot</groupId >
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7</version>
<relativePath/> <!-- lookup parent from update -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding >UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
複製程式碼
II. Filter教程
1. 使用說明
在SpringBoot專案中,如果需要自定義一個Filter,並沒有什麼特殊的地方,直接實現介面即可,比如下面一個輸出請求日誌的攔截器
@Slf4j
@WebFilter
public class ReqFilter implements Filter {
public ReqFilter() {
System.out.println("init reqFilter");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
throws IOException,ServletException {
HttpServletRequest req = (HttpServletRequest) request;
log.info("url={},params={}",req.getRequestURI(),JSON.toJSONString(req.getParameterMap()));
chain.doFilter(req,response);
}
@Override
public void destroy() {
}
}
複製程式碼
實現一個自定義的Filter容易,一般有兩個步驟
- 實現 Filter 介面
- 在
doFilter
方法中新增業務邏輯,如果允許訪問繼續,則執行chain.doFilter(req,response);
; 不執行上面這一句,則訪問到此為止
接下來的一個問題就是如何讓我們自定義的Filter生效,在SpringBoot專案中,有兩種常見的使用方式
- @WebFilter
- 包裝Bean:
FilterRegistrationBean
a. WebFilter
這個註解屬於Servlet3+,與Spring也沒有什麼關係,所以問題來了,當我在Filter上添加了這個註解之後,Spring怎麼讓它生效呢?
- 配置檔案中顯示使用註解
@ServletComponentScan
@ServletComponentScan
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
複製程式碼
WebFilter常用屬性如下,其中urlPatterns
最為常用,表示這個filter適用於哪些url請求(預設場景下全部請求都被攔截)
屬性名 | 型別 | 描述 |
---|---|---|
filterName | String | 指定過濾器的 name 屬性,等價於 |
value | String[] | 該屬性等價於 urlPatterns 屬性。但是兩者不應該同時使用。 |
urlPatterns | String[] | 指定一組過濾器的 URL 匹配模式。等價於 標籤。 |
servletNames | String[] | 指定過濾器將應用於哪些 Servlet。取值是 @WebServlet 中的 name 屬性的取值,或者是 web.xml 中 的取值。 |
dispatcherTypes | DispatcherType | 指定過濾器的轉發模式。具體取值包括:ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。 |
initParams | WebInitParam[] | 指定一組過濾器初始化引數,等價於 標籤。 |
asyncSupported | boolean | 宣告過濾器是否支援非同步操作模式,等價於 標籤。 |
description | String | 該過濾器的描述資訊,等價於 標籤。 |
displayName | String | 該過濾器的顯示名,通常配合工具使用,等價於 標籤。 |
b. FilterRegistrationBean
上面一種方式比較簡單,後面會說到有個小問題,指定Filter的優先順序比較麻煩,
下面是使用包裝bean註冊方式
@Bean
public FilterRegistrationBean<OrderFilter> orderFilter() {
FilterRegistrationBean<OrderFilter> filter = new FilterRegistrationBean<>();
filter.setName("reqFilter");
filter.setFilter(new ReqFilter());
// 指定優先順序
filter.setOrder(-1);
return filter;
}
複製程式碼
2. 常見問題
上面整完,就可以開始測試使用過濾器了,在進入實測環節之前,先來看兩個常見的問題
- Filter作為Servelt的元件,怎麼與SpringBoot中的Bean互動
- 多個Filter之間的優先順序怎麼確定
a. Filter依賴Bean注入問題
如果有小夥伴使用SpringMVC + web.xml方式來定義Filter,就會發現自定義的Filter中無法通過@Autowired
方式來注入Spring的bean
我之前使用的是spring4 Servlet2+ ,存在上面的問題,如果有不同觀點請留言告訴我,感謝
SpringBoot中可以直接注入依賴的Bean,從上面的第二種註冊方式可以看到,Spring將Filter封裝成了一個Bean物件,因此可以直接注入依賴的Bean
下面定義一個AuthFilter
,依賴了自定義的DemoBean
@Data
@Component
public class DemoBean {
private long time;
public DemoBean() {
time = System.currentTimeMillis();
}
public void show() {
System.out.println("demo bean!!! " + time);
}
}
@Slf4j
@WebFilter
public class AuthFilter implements Filter {
@Autowired
private DemoBean demoBean;
public AuthFilter() {
System.out.println("init autFilter");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request,ServletException {
log.info("in auth filter! {}",demoBean);
// 測試,用header中的 tx-demo 來判斷是否為認證的請求
HttpServletRequest req = (HttpServletRequest) request;
String auth = req.getHeader("tx-demo");
if ("yihuihui".equals(auth)) {
// 只有認證的請求才允許訪問,請求頭中沒有這個時,不執行下面的的方法,則表示請求被過濾了
// 在測試優先順序時開啟下面的註釋
// chain.doFilter(request,response);
} else {
chain.doFilter(request,response);
}
}
@Override
public void destroy() {
}
}
複製程式碼
b. 優先順序指定
Filter的優先順序指定,通過我的實際測試,@Order
註解沒有用,繼承 Ordered
介面也沒有用,再不考慮web.xml的場景下,只能通過在註冊Bean的時候指定優先順序
例項如下,三個Filter,兩個通過@WebFilter
註解方式註冊,一個通過FilterRegistrationBean
方式註冊
@Slf4j
@Order(2)
@WebFilter
public class AuthFilter implements Filter,Ordered {
...
}
@Slf4j
@Order(1)
@WebFilter
public class ReqFilter implements Filter,Ordered {
...
}
@Slf4j
public class OrderFilter implements Filter {
}
@ServletComponentScan
@SpringBootApplication
public class Application {
@Bean
public FilterRegistrationBean<OrderFilter> orderFilter() {
FilterRegistrationBean<OrderFilter> filter = new FilterRegistrationBean<>();
filter.setName("orderFilter");
filter.setFilter(new OrderFilter());
filter.setOrder(-1);
return filter;
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
複製程式碼
3. 測試
上面定義了三個Filter,我們主要驗證下優先順序,如果@Order
註解生效,那麼執行的先後順序應該是
OrderFilter -> ReqFilter -> AuthFilter
如果不是上面的順序,那麼說明@Order
註解沒有用
@RestController
public class IndexRest {
@GetMapping(path = {"/","index"})
public String hello(String name) {
return "hello " + name;
}
}
複製程式碼
(上文截圖原始碼來自: org.apache.catalina.core.ApplicationFilterFactory#createFilterChain
)
上面是測試時關鍵鏈路的斷點截圖,從陣列中可以看出 AuthFilter
的優先順序大於ReqFilter
, 下面實際的輸出也說明瞭@Order
註解不能指定Filter的優先順序(不知道為什麼網路上有大量使用Order來指定Filer優先順序的文章!!!)
接下來我們的問題就是WebFilter
註解來註冊的Filter的優先順序是怎樣的呢,我們依然通過debug來看,關鍵程式碼路徑為: org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize
- OrderFiler是我們手動註冊並設定優先順序為-1
- ReqFilter,AuthFilter通過 WebFillter方式註冊,預設優先順序為
2147483647
,相同優先順序的情況下,根據名字先後順序來決定
III. 小結
本文主要介紹了過濾器Filter的使用方式,以及常見的兩個問題解答,文中內容穿插了一點原始碼的分析截圖,並未深入,如有興趣的同學可以根據文中提的幾個關鍵位置探索一番
下面簡單小結下文中內容
1. Filter使用
自定義Filter的實現
- 實現Filter介面
- doFilter方法中,顯示呼叫
chain.doFilter(request,response);
表示請求繼續;否則表示請求被過濾
註冊生效
-
@ServletComponentScan
自動掃描帶有@WebFilter
註解的Filter - 建立Bean:
FilterRegistrationBean
來包裝自定義的Filter
2. IoC/DI
在SpringBoot中Filter可以和一般的Bean一樣使用,直接通過Autowired
注入其依賴的Spring Bean物件
3. 優先順序
通過建立FilterRegistrationBean
的時候指定優先順序,如下
@Bean
public FilterRegistrationBean<OrderFilter> orderFilter() {
FilterRegistrationBean<OrderFilter> filter = new FilterRegistrationBean<>();
filter.setName("orderFilter");
filter.setFilter(new OrderFilter());
filter.setOrder(-1);
return filter;
}
複製程式碼
此外格外注意,@WebFilter
宣告的Filter,優先順序為2147483647
(最低優先順序)
- @Order註解不能指定Filter優先順序
- @Order註解不能指定Filter優先順序
- @Order註解不能指定Filter優先順序
IV. 其他
web系列博文
- 191012-SpringBoot系列教程web篇之自定義異常處理HandlerExceptionResolver
- 191010-SpringBoot系列教程web篇之全域性異常處理
- 190930-SpringBoot系列教程web篇之404、500異常頁面配置
- 190929-SpringBoot系列教程web篇之重定向
- 190913-SpringBoot系列教程web篇之返回文字、網頁、圖片的操作姿勢
- 190905-SpringBoot系列教程web篇之中文亂碼問題解決
- 190831-SpringBoot系列教程web篇之如何自定義引數解析器
- 190828-SpringBoot系列教程web篇之Post請求引數解析姿勢彙總
- 190824-SpringBoot系列教程web篇之Get請求引數解析姿勢彙總
- 190822-SpringBoot系列教程web篇之Beetl環境搭建
- 190820-SpringBoot系列教程web篇之Thymeleaf環境搭建
- 190816-SpringBoot系列教程web篇之Freemaker環境搭建
- 190421-SpringBoot高階篇WEB之websocket的使用說明
- 190327-Spring-RestTemplate之urlencode引數解析異常全程分析
- 190317-Spring MVC之基於java config無xml配置的web應用構建
- 190316-Spring MVC之基於xml配置的web應用構建
- 190213-SpringBoot檔案上傳異常之提示The temporary upload location xxx is not valid
專案原始碼
1. 一灰灰Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰Blog個人部落格 blog.hhui.top
- 一灰灰Blog-Spring專題部落格 spring.hhui.top