Filter可以視作是servlet的加強版,主要用作對用戶的請求進行預處理,或者對返回給客戶端的結果進行再次加工,是一個典型的鏈式處理模式。本篇簡單介紹filter的基本使用方法,主要涉及以下內容:
- Filter的背景知識
- 使用Filter的流程
- Filter的生命周期
- 一個完整的實例
一、Filter的簡單介紹
Filter在英文中是過濾器的意思,當然在此處的使用也是完美的切合了它的意思,我們使用filter的主要目的就是完成一個過濾的作用。可以在一個請求到達servlet之前,將其截取進行邏輯判斷,然后決定是否放行到請求的servlet。也可以在一個response到達客戶端之前,截取結果進行邏輯判斷,然后決定是否允許返回給客戶端。所以filter有如下幾個種類:
- 用戶授權的filter:filter負責判斷用戶是否有權限請求該頁面,給予過濾判斷
- 日志filter:截取某個用戶在本網站上的所有請求,記錄軌跡
- 負責解碼的filter:規定處理本次請求的解碼方式
最后需要注意的是,一個filter過濾器可以加在多個servlet控制器上,當然多個filter過濾器也是可以加在一個servlet控制器上的。由此也是可以看出來,我們使用filter往往是對一些公共的操作進行處理,例如:判斷用戶權限,解碼本次請求等,還比如,我們的web應用中某些頁面是需要用戶登錄后才能訪問的,以往我們都是在每個servlet頁面加上判斷控制,導致代碼冗余,有了filter,我們可以定義一個實現了filter的過濾器,讓需要判斷是否登錄的頁面都加上這么一個過濾器,可以大大降低代碼的冗余程度。
二、Filter的使用流程
在Java中如果想要自定義一個filter過濾器的話,需要繼承Javax.servlet.Filter接口,這個接口中只有三個方法:
default void init(FilterConfig filterConfig)
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3)
default void destroy()
其中init和destroy方法是有默認實現的,也就是我們不是必須重寫這兩個方法,但是doFilter 這個方法是一個核心的方法,是我們必須要實現的。首先我們看init方法的作用,這個方法是用來初始化filter實例的,也就是當用戶請求了某個攔截器而此攔截器又匹配了某個過濾器,此時web容器就會定位到該過濾器然后創建該filter類的實例對象並調用此實例的init方法,完成初始化工作。對於destroy方法毋庸置疑就是在過濾器執行結束的時候調用,主要完成對一些資源的釋放。下面主要看dofilter這個方法。
doFilter方法是filter接口中的核心方法,一旦創建完該過濾器的實例之后,會執行dofilter方法,所有的過濾邏輯都是在此方法中進行的。主要有三個參數,第一個參數是一個ServletRequest對象,HttpServletRequest繼承於此接口,當用戶請求某個攔截器的時候,檢測到此請求存在過濾器,於是會封裝好本次請求的相關數據,傳遞給dofilter的ServletRequest參數,ServletResponse參數的來源和ServletRequest是一樣的,都是由客戶端封裝過來的。至於第三個參數,這是一個FilterChain處理鏈,詳細的介紹等說明了web.xml中配置filter之后。
第一步如上,創建一個繼承自filter接口的類,並實現其中的三個方法。第二步是在web.xml中配置該類用於過濾哪些攔截器。web.xml代碼如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--定義filter-->
<filter>
<filter-name>isLogin</filter-name>
<filter-class>Test_f.MyFilter</filter-class>
</filter>
<!--定義filter攔截的地址-->
<filter-mapping>
<filter-name>isLogin</filter-name>
<url-pattern>/a</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>isLogin</filter-name>
<url-pattern>/b</url-pattern>
</filter-mapping>
</web-app>
如上述的代碼,我們需要兩個操作,首先是定義一個filter,指定了該filter的name和相對應的過濾器類。然后我們可以通過filter-mapping映射過濾器和URL,此處使用了兩個映射,對該過濾器指定了對路徑名為/a和/b的請求進行攔截。當然這個url-pattern元素的值可以有以下三種形式,完全匹配(/a/b等),目錄匹配(/* 、 /abc/等),擴展名(.a,*.b等)。
了解了配置filter的主要操作之后,我們回去看過濾器類,我們說init方法是在首次創建filter實例的時候,用於執行初始化操作的,其中有個參數FilterConfig ,這是當前filter的配置信息,其中方法如下:
String getFilterName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
其實在創建filter實例的時候,web容器會將此實例對應在web.xml中的配置信息讀取並封裝成一個FilterConfig 對象傳遞給init方法,getFilterName方法就是我們在web.xml中配置的filter-name,getServletContext會獲取當前servlet的上下文,當我們在定義filter的時候使用<init-param>來定義一些初始化參數的時候,就可以使用此方法來獲取這些初始化參數。getInitParameterNames方法用於獲取所有初始化參數的枚舉集合。這樣我們在init方法中就可以獲取這些配置參數,初始化filter實例。
上面我們只定義了一個filter,如果我們對於一次請求需要執行多個filter,進行過濾操作的話,web容器會在你請求某個URL的時候,在web.xml中找到所有匹配的filter,按照注冊的順序以FilterChain 鏈的形式傳遞到方法doFilter的第三個參數中,而這個filterChain中只有一個方法:
void doFilter(ServletRequest var1, ServletResponse var2)
如果我們在 void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) 方法中調用var3.doFilter(var1,var2),就代表此filter實例結束,則web服務器會檢查FilterChain對象中是否還有filter對象(因為這是一個鏈,filter的數量是大於等於一的),如果沒有就會放行,直接調用目標地址,如果還有filter對象,就會轉而執行下一個filter。正是由於這樣的機制,我們才可以對於一個URL請求添加多個filter過濾器。
三、一個簡單的實例
下面通過一個簡單的實例直觀的感受下filter過濾器的作用:
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException{
HttpServletRequest req = (HttpServletRequest)var1;
HttpSession session = req.getSession();
String state = (String)session.getAttribute("state");
if(state.equals("1")){
var3.doFilter(var1,var2);
}else{
HttpServletResponse response = (HttpServletResponse)var2;
response.sendRedirect("Error_page.html");
}
}
}
//web.xml
<filter>
<filter-name>isLogin</filter-name>
<filter-class>Test_f.MyFilter</filter-class>
</filter>
<!--定義filter攔截的地址-->
<filter-mapping>
<filter-name>isLogin</filter-name>
<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<h1>這是index頁面</h1>
</body>
</html>
//set.jsp
//為了使程序簡單,我們采用手動設置session
//在實際的項目中,當用戶登錄之后自動設置session
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<%pageContext.getSession().setAttribute("state","0");%>
</body>
</html>
我們首先訪問set.jsp頁面設置本次會話的session值,然后修改瀏覽器地址欄訪問index.jsp頁面

敲下回車鍵,結果如下:

我們先看,敲下回車鍵之后,程序怎么執行的,因為我們在web.xml中配置了MyFilter的攔截URL為index.jsp,所以當我們訪問index.jsp的時候,會創建MyFilter 實例對象,封裝配置信息到FilterConfig對象中,然后封裝request請求和response,還有從web.xml 中讀取的FilterChain對象傳入MyFilter的doFilter方法中,我們在其中獲取本次會話的session對象,取得其中的數據,如果為一放行,否則跳轉到錯誤頁面。此處的state的session值為0,我們在set.jsp中設置的,大家也可以在set.jsp頁面設置其值為1,這樣最終的結果會是這樣的:

上述的demo只是為了簡單的演示,其實使用filter可以完成大大的降低我們的代碼的冗余程度。這個例子是為了演示,所以很簡單。
四、Filter 的生命周期
當用戶請求某個頁面時候,會到web.xml中匹配是否存在能夠匹配上此次請求的filter,如果有封裝它的配置信息,FilterChain鏈。然后調用init方法,完成初始化,接着調用dofilter方法,處理核心邏輯,當此實例被銷毀的時候,會調用destroy方法。
本篇文章結束,如果有理解錯誤,望大家指出!
