前面一篇博文介紹了在 SpringBoot 中使用 Filter 的兩種使用方式,這里介紹另外一種直接將 Filter 當做 Spring 的 Bean 來使用的方式,並且在這種使用方式下,Filter 的優先級可以直接通過@Order
注解來指定;最后將從源碼的角度分析一下兩種不同的使用方式下,為什么@Order
注解一個生效,一個不生效
本篇博文強烈推薦與上一篇關聯閱讀,可以 get 到更多的知識點: 191016-SpringBoot 系列教程 web 篇之過濾器 Filter 使用指南
I. Filter
本篇博文的工程執行的環境依然是SpringBoot2+
, 項目源碼可以在文章最后面 get
1. 使用姿勢
前面一篇博文,介紹了兩種使用姿勢,下面簡單介紹一下
WebFilter 注解
在 Filter 類上添加注解@WebFilter
;然后再項目中,顯示聲明@ServletComponentScan
,開啟 Servlet 的組件掃描
@WebFilter
public class SelfFilter implements Filter {
}
@ServletComponentScan
public class SelfAutoConf {
}
FilterRegistrationBean
另外一種方式則是直接創建一個 Filter 的注冊 Bean,內部持有 Filter 的實例;在 SpringBoot 中,初始化的是 Filter 的包裝 Bean 就是這個
@Bean
public FilterRegistrationBean<OrderFilter> orderFilter() {
FilterRegistrationBean<OrderFilter> filter = new FilterRegistrationBean<>();
filter.setName("orderFilter");
filter.setFilter(new SelfFilter());
filter.setOrder(-1);
return filter;
}
本篇將介紹另外一種方式,直接將 Filter 當做普通的 Bean 對象來使用,也就是說,我們直接在 Filter 類上添加注解@Component
即可,然后 Spring 會將實現 Filter 接口的 Bean 當做過濾器來注冊
而且這種使用姿勢下,Filter 的優先級可以通過@Order
注解來指定;
設計一個 case,定義兩個 Filter(ReqFilter
和OrderFilter
), 當不指定優先級時,根據名字來,OrderFilter 優先級會更高;我們主動設置下,希望ReqFilter
優先級更高
@Order(1)
@Component
public class ReqFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("req filter");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
@Order(10)
@Component
public class OrderFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("order filter!");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
2. 優先級測試
上面兩個 Filter 直接當做了 Bean 來寫入,我們寫一個簡單的 rest 服務來測試一下
@RestController
public class IndexRest {
@GetMapping(path = {"/", "index"})
public String hello(String name) {
return "hello " + name;
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
請求之后輸出結果如下, ReqFilter 優先執行了
II. 源碼分析
當我們直接將 Filter 當做 Spring Bean 來使用時,@Order
注解來指定 Filter 的優先級沒有問題;但是前面一篇博文中演示的@WebFilter
注解的方式,則並不會生效
- 這兩種方式的區別是什么?
@Order
注解到底有什么用,該怎么用
1. Bean 方式
首先我們分析一下將 Filter 當做 Spring bean 的使用方式,我們的目標放在 Filter 的注冊邏輯上
第一步將目標放在: org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize
下面的邏輯中包括了 ServeltContext 的初始化,而我們的 Filter 則可以看成是屬於 Servlet 的 Bean
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
beanFactory);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
getServletContext());
existingScopes.restore();
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
getServletContext());
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
注意上面代碼中的 for 循環,在執行getServletContextInitializerBeans()
的時候,Filter 就已經注冊完畢,所以我們需要再深入進去
將目標集中在org.springframework.boot.web.servlet.ServletContextInitializerBeans#ServletContextInitializerBeans
public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
this.initializers = new LinkedMultiValueMap<>();
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = this.initializers.values()
.stream()
.flatMap((value) -> value.stream()
.sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
}
上面有兩行代碼比較突出,下面單獨撈出來了,需要我們重點關注
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);
通過斷點進來,發現第一個方法只是注冊了dispatcherServletRegistration
;接下來重點看第二個
@SuppressWarnings("unchecked")
private void addAdaptableBeans(ListableBeanFactory beanFactory) {
MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
addAsRegistrationBean(beanFactory, Servlet.class,
new ServletRegistrationBeanAdapter(multipartConfig));
addAsRegistrationBean(beanFactory, Filter.class,
new FilterRegistrationBeanAdapter());
for (Class<?> listenerType : ServletListenerRegistrationBean
.getSupportedTypes()) {
addAsRegistrationBean(beanFactory, EventListener.class,
(Class<EventListener>) listenerType,
new ServletListenerRegistrationBeanAdapter());
}
}
從上面調用的方法命名就可以看出,我們的 Filter 注冊就在addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
上面的截圖就比較核心了,在創建FilterRegistrationBean
的時候,根據 Filter 的順序來指定最終的優先級
然后再回到構造方法中,根據 order 進行排序, 最終確定 Filter 的優先級
2. WebFilter 方式
接下來我們看一下 WebFilter 方式為什么不生效,在根據我的項目源碼進行測試的時候,請將需要修改一下自定義的 Filter,將類上的@WebFilter
注解打開,@Component
注解刪除,並且打開 Application 類上的ServletComponentScan
我們這里 debug 的路徑和上面的差別不大,重點關注下面ServletContextInitializerBeans
的構造方法上面
當我們深入addServletContextInitializerBeans(beanFactory);
這一行進去 debug 的時候,會發現我們自定義的 Filter 是在這里面完成初始化的;而前面的使用方式,則是在addAdapterBeans()
方法中初始化的,如下圖
在getOrderedBeansOfType(beanFactory, ServletContextInitializer.class)
的調用中就返回了我們自定義的 Bean,也就是說我們自定義的 Filter 被認為是ServletContextInitializer
的類型了
然后我們換個目標,看一下 ReqFilter 在注冊的時候是怎樣的
關鍵代碼: org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
(因為 bean 很多,所以我們可以加上條件斷點)
通過斷點調試,可以知道我們的自定義 Filter 是通過
WebFilterHandler
類掃描注冊的, 對這一塊管興趣的可以深入看一下org.springframework.boot.web.servlet.ServletComponentRegisteringPostProcessor#scanPackage
上面只是聲明了 Bean 的注冊信息,但是還沒有具體的實例化,接下來我們回到前面的進程,看一下 Filter 的實例過程
private <T> List<Entry<String, T>> getOrderedBeansOfType(
ListableBeanFactory beanFactory, Class<T> type, Set<?> excludes) {
Comparator<Entry<String, T>> comparator = (o1,
o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(),
o2.getValue());
String[] names = beanFactory.getBeanNamesForType(type, true, false);
Map<String, T> map = new LinkedHashMap<>();
for (String name : names) {
if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
T bean = beanFactory.getBean(name, type);
if (!excludes.contains(bean)) {
map.put(name, bean);
}
}
}
List<Entry<String, T>> beans = new ArrayList<>();
beans.addAll(map.entrySet());
beans.sort(comparator);
return beans;
}
注意我們的 Filter 實例在T bean = beanFactory.getBean(name, type);
通過這種方式獲取的 Filter 實例,並不會將 ReqFilter 類上的 Order 注解的值,來更新FilterRegistrationBean
的 order 屬性,所以這個注解不會生效
最后我們再看一下,通過 WebFilter 的方式,容器類不會存在ReqFilter.class
類型的 Bean, 這個與前面的方式不同
III. 小結
本文主要介紹了另外一種 Filter 的使用姿勢,將 Filter 當做普通的 Spring Bean 對象進行注冊,這種場景下,可以直接使用@Order
注解來指定 Filter 的優先級
但是,這種方式下,我們的 Filter 的很多基本屬性不太好設置,一個方案是參考 SpringBoot 提供的一些 Fitler 的寫法,在 Filter 內部來實現相關邏輯
0. 項目
web 系列博文
- 191016-SpringBoot 系列教程 web 篇之過濾器 Filter 使用指南
- 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
項目源碼
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 項目:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/210-web-filter
1. 一灰灰 Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰 Blog 個人博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 專題博客 http://spring.hhui.top