前面一篇博文介紹了在 SpringBoot 中使用 Filter 的兩種使用方式,這裏介紹另一種直接將 Filter 當作 Spring 的 Bean 來使用的方式,而且在這種使用方式下,Filter 的優先級能夠直接經過@Order
註解來指定;最後將從源碼的角度分析一下兩種不一樣的使用方式下,爲何@Order
註解一個生效,一個不生效java
本篇博文強烈推薦與上一篇關聯閱讀,能夠 get 到更多的知識點: 191016-SpringBoot 系列教程 web 篇之過濾器 Filter 使用指南git
<!-- more -->github
本篇博文的工程執行的環境依然是SpringBoot2+
, 項目源碼能夠在文章最後面 getweb
前面一篇博文,介紹了兩種使用姿式,下面簡單介紹一下spring
WebFilter 註解websocket
在 Filter 類上添加註解@WebFilter
;而後再項目中,顯示聲明@ServletComponentScan
,開啓 Servlet 的組件掃描app
@WebFilter public class SelfFilter implements Filter { } @ServletComponentScan public class SelfAutoConf { }
FilterRegistrationBeansocket
另一種方式則是直接建立一個 Filter 的註冊 Bean,內部持有 Filter 的實例;在 SpringBoot 中,初始化的是 Filter 的包裝 Bean 就是這個ide
@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 當作過濾器來註冊spring-boot
並且這種使用姿式下,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() { } }
上面兩個 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 優先執行了
當咱們直接將 Filter 當作 Spring Bean 來使用時,@Order
註解來指定 Filter 的優先級沒有問題;可是前面一篇博文中演示的@WebFilter
註解的方式,則並不會生效
@Order
註解到底有什麼用,該怎麼用首先咱們分析一下將 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 的優先級
接下來咱們看一下 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, 這個與前面的方式不一樣
本文主要介紹了另一種 Filter 的使用姿式,將 Filter 當作普通的 Spring Bean 對象進行註冊,這種場景下,能夠直接使用@Order
註解來指定 Filter 的優先級
可是,這種方式下,咱們的 Filter 的不少基本屬性不太好設置,一個方案是參考 SpringBoot 提供的一些 Fitler 的寫法,在 Filter 內部來實現相關邏輯
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛