SpringBoot系列教程web篇之過濾器Filter使用指南


web三大組件之一Filter,可以說是很多小伙伴學習java web時最早接觸的知識點了,然而學得早不代表就用得多。基本上,如果不是讓你從0到1寫一個web應用(或者說即便從0到1寫一個web應用),在你的日常業務開發中不太可能碰到需要手寫Filter的場景

本文將簡單介紹寫什么是Filter,以及在SpringBoot中使用Filter的一般姿勢與常見問題

原文查看: SpringBoot系列教程web篇之過濾器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, ServletResponse response, FilterChain chain)
            throws IOException, 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系列博文

項目源碼

1. 一灰灰Blog

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛

一灰灰blog


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM