【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
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>
標籤。