1. 程式人生 > >【Java學習筆記】65:認識Filter(過濾器),FilterChain(過濾鏈)及其實現

【Java學習筆記】65:認識Filter(過濾器),FilterChain(過濾鏈)及其實現

Filter和Servlet、Listener一同作為Java web開發的三大元件。runoob中將Filter歸為Servlet,實際上Filter元件和Servlet元件是兩回事,但Filter介面等確實是在javax.servlet.*中,大概可以認為它也屬於servlet技術。

Filter的成員方法

Filter可以對web伺服器上的web資源(如JSP頁面、HTML頁面等)進行攔截,當客戶端要訪問這些資源時,可以通過建立若干個Filter來實現複雜的過濾功能,以實現如訪問許可權控制、訪問日誌等功能。
這裡寫圖片描述
一個Filter,實際上就是一個實現了Filter介面並覆寫了其三個public void方法的類。

其中init(FilterConfig xx)方法會將web伺服器(唯一一次地)建立了本Filter的物件並讀取到的web.xml中與這個Filter有關的配置所存至的FilterConfig型別的物件作為引數傳進來,然後就會(唯一一次地)呼叫該方法,因此該方法是用這個引數中的配置資訊來對本Filter做初始化的。開發者寫如何初始化時可能會用到的本Filter的配置資訊,都在這個FilterConfig型別的引數中。

doFilter(ServletRequest xx, ServletResponse xx, FilterChain xx)方法是核心的過濾操作。第一個引數是客戶端傳來的但還未到達要呼叫的Servlet

的Servlet請求,在該方法中可以被修改;第二個引數是web伺服器要返回給客戶但還未到達客戶端的Servlet響應,在該方法中可以被修改;第三個引數是過濾鏈,由容器實現並將其例項作為引數傳進來,這個引數可以呼叫doFilter(Servlet請求,Servlet響應)成員方法,為已經經過本doFilter方法體處理後的Servlet請求和Servlet響應放行,即去轉而執行過濾鏈中下一個Filter的doFilter方法。顯然,只要為這個FilterChain物件引數的doFilter方法的呼叫設定障礙,就能實現“過濾”的功能,因為在任一Filter處被攔截,Servlet請求/響應都不會到達web伺服器/客戶端。

當Filter生命週期結束時web伺服器會(唯一一次地)呼叫destroy()方法,可以在這裡釋放本Filter使用的資源。

web.xml中有關Filter的配置

Filter一般配置在所有的Servlet之前。

<filter>
    <filter-name>過濾器的名稱</filter-name>
    <filter-class>過濾器所在的包.過濾器的類名</filter-class>
    <init-param>
        <param-name>初始化引數名</param-name>
        <param-value>初始化引數值</param-value>
    </init-param>
</filter>

<filter>
    ......
</filter>

......

<filter-mapping>
    <filter-name>過濾器的名稱</filter-name>
    <url-pattern>
        寫/*表示與所有資源關聯,
        寫xxx(資源名)表示與該資源關聯,
        寫Servlet名稱表示與該Servlet關聯
    </url-pattern>
</filter-mapping>

<filter-mapping>
    ......
</filter-mapping>

......

使用Filter

doFilter內過濾鏈放行的doFilter成員方法之前做對客戶端發來的Servlet請求的攔截和處理,而在放行之後做對web伺服器的Servlet響應的攔截和處理。

myFilter下Filter1.java

package myFilter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class Filter1 implements Filter {
    @Override
    public void init(FilterConfig fc) throws ServletException {
        // 獲取配置的網站名稱
        String site = fc.getInitParameter("Site");
        System.out.println("過濾器初始化了,獲取了網站名稱是" + site);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain fc) throws IOException, ServletException {
        System.out.println("攔截客戶端向伺服器發的請求");
        // 把Servlet請求和迴應傳給過濾鏈上的下一過濾器
        fc.doFilter(req, resp);
        System.out.println("攔截伺服器向客戶端發的響應");
    }

    @Override
    public void destroy() {
        System.out.println("過濾器要被移除了");
    }
}

配置的web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>FilterTest</display-name>
<filter>
    <filter-name>Filter1</filter-name>
    <filter-class>myFilter.Filter1</filter-class>
    <init-param>
        <param-name>Site</param-name>
        <param-value>Filter測試</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>Filter1</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
    <welcome-file>go.html</welcome-file>
</welcome-file-list>
</web-app>

測試資源go.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>標題啊啊啊</title>
</head>
<body>
    正文啊啊啊啊啊啊啊啊啊啊
</body>
</html>

執行結果

開啟Tomcat時可以在看到夾雜其中的資訊:

......
過濾器初始化了,獲取了網站名稱是Filter測試
......

說明Filter的物件是web容器在初始化時建立的,並在初始化時去呼叫了init方法。

當用瀏覽器訪問資源http://localhost:8080/FilterTest/go.html時,控制檯又輸出了:

攔截客戶端向伺服器發的請求
攔截伺服器向客戶端發的響應

當然這裡並不是真的做了攔截,這說明了doFilter方法的呼叫時機正是在客戶端請求資源,並在過濾鏈到達該過濾器時執行的。如果再次用瀏覽器訪問這個資源,會繼續輸出這兩句話。

當Tomcat關閉時,理所當然地夾雜了destroy方法中輸出的資訊:

......
過濾器要被移除了
......

FilterChain(過濾鏈)

通過修改上面的那個例子,構成兩個Filter的過濾鏈,驗證過濾器的組織順序和web.xml中的filter-mapping標籤順序一致,驗證doFilter內的過濾鏈的doFilter方法前後分別是對Servlet請求和Servlet響應的攔截(執行時機)。

myFilter下Filter1.java

package myFilter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class Filter1 implements Filter {
    @Override
    public void init(FilterConfig fc) throws ServletException {
        // 獲取配置的網站名稱
        String who = fc.getInitParameter("who");
        System.out.println("Filter1初始化了,獲取了who引數是" + who);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain fc) throws IOException, ServletException {
        System.out.println("Filter1攔截客戶端向伺服器發的請求");
        // 把Servlet請求和迴應傳給過濾鏈上的下一過濾器
        fc.doFilter(req, resp);
        System.out.println("Filter1攔截伺服器向客戶端發的響應");
    }

    @Override
    public void destroy() {
        System.out.println("Filter1要被移除了");
    }
}

myFilter下Filter2.java

package myFilter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class Filter2 implements Filter {
    @Override
    public void init(FilterConfig fc) throws ServletException {
        // 獲取配置的網站名稱
        String who = fc.getInitParameter("who");
        System.out.println("Filter2初始化了,獲取了who引數是" + who);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain fc) throws IOException, ServletException {
        System.out.println("Filter2攔截客戶端向伺服器發的請求");
        // 把Servlet請求和迴應傳給過濾鏈上的下一過濾器
        fc.doFilter(req, resp);
        System.out.println("Filter2攔截伺服器向客戶端發的響應");
    }

    @Override
    public void destroy() {
        System.out.println("Filter2要被移除了");
    }
}

配置的web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>FilterTest</display-name>

<filter>
    <filter-name>Filter1</filter-name>
    <filter-class>myFilter.Filter1</filter-class>
    <init-param>
        <param-name>who</param-name>
        <param-value>汪汪汪汪</param-value>
    </init-param>
</filter>

<filter>
    <filter-name>Filter2</filter-name>
    <filter-class>myFilter.Filter2</filter-class>
    <init-param>
        <param-name>who</param-name>
        <param-value>喵喵喵喵</param-value>
    </init-param>
</filter>

<!-- 故意將filter-mapping倒過來寫,觀察順序是和哪個標籤一致 -->

<filter-mapping>
    <filter-name>Filter2</filter-name>
    <!-- 只過濾根目錄下go.html -->
    <url-pattern>/go.html</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name>Filter1</filter-name>
    <!-- 過濾根目錄下所有資源(實驗中只放了go.html) -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

<welcome-file-list>
    <welcome-file>go.html</welcome-file>
</welcome-file-list>
</web-app>

執行結果

開啟Tomcat時:

......
Filter1初始化了,獲取了who引數是汪汪汪汪
Filter2初始化了,獲取了who引數是喵喵喵喵
......

訪問http://localhost:8080/FilterTest/時:

Filter2攔截客戶端向伺服器發的請求
Filter1攔截客戶端向伺服器發的請求
Filter1攔截伺服器向客戶端發的響應
Filter2攔截伺服器向客戶端發的響應

關閉Tomcat時:

......
Filter1要被移除了
Filter2要被移除了
......

非常值得注意的一點就是,過濾鏈的組織順序(從客戶端->伺服器)是和web.xml中的<filter-mapping></filter-mapping>標籤的順序一致,而不是<filter></filter>標籤。