?????Filter可以視作是servlet的加強(qiáng)版董朝,主要用作對(duì)用戶的請(qǐng)求進(jìn)行預(yù)處理周偎,或者對(duì)返回給客戶端的結(jié)果進(jìn)行再次加工,是一個(gè)典型的鏈?zhǔn)教幚砟J健1酒?jiǎn)單介紹filter的基本使用方法蛮穿,主要涉及以下內(nèi)容:
- Filter的背景知識(shí)
- 使用Filter的流程
- Filter的生命周期
- 一個(gè)完整的實(shí)例
一、Filter的簡(jiǎn)單介紹
?????Filter在英文中是過(guò)濾器的意思宋舷,當(dāng)然在此處的使用也是完美的切合了它的意思绪撵,我們使用filter的主要目的就是完成一個(gè)過(guò)濾的作用∽r穑可以在一個(gè)請(qǐng)求到達(dá)servlet之前,將其截取進(jìn)行邏輯判斷幻碱,然后決定是否放行到請(qǐng)求的servlet绎狭。也可以在一個(gè)response到達(dá)客戶端之前,截取結(jié)果進(jìn)行邏輯判斷褥傍,然后決定是否允許返回給客戶端儡嘶。所以filter有如下幾個(gè)種類:
- 用戶授權(quán)的filter:filter負(fù)責(zé)判斷用戶是否有權(quán)限請(qǐng)求該頁(yè)面,給予過(guò)濾判斷
- 日志filter:截取某個(gè)用戶在本網(wǎng)站上的所有請(qǐng)求恍风,記錄軌跡
- 負(fù)責(zé)解碼的filter:規(guī)定處理本次請(qǐng)求的解碼方式
最后需要注意的是蹦狂,一個(gè)filter過(guò)濾器可以加在多個(gè)servlet控制器上,當(dāng)然多個(gè)filter過(guò)濾器也是可以加在一個(gè)servlet控制器上的朋贬。由此也是可以看出來(lái)凯楔,我們使用filter往往是對(duì)一些公共的操作進(jìn)行處理,例如:判斷用戶權(quán)限锦募,解碼本次請(qǐng)求等摆屯,還比如,我們的web應(yīng)用中某些頁(yè)面是需要用戶登錄后才能訪問(wèn)的糠亩,以往我們都是在每個(gè)servlet頁(yè)面加上判斷控制虐骑,導(dǎo)致代碼冗余,有了filter赎线,我們可以定義一個(gè)實(shí)現(xiàn)了filter的過(guò)濾器廷没,讓需要判斷是否登錄的頁(yè)面都加上這么一個(gè)過(guò)濾器,可以大大降低代碼的冗余程度垂寥。
二颠黎、Filter的使用流程
?????在Java中如果想要自定義一個(gè)filter過(guò)濾器的話,需要繼承Javax.servlet.Filter接口矫废,這個(gè)接口中只有三個(gè)方法:
default void init(FilterConfig filterConfig)
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3)
default void destroy()
其中init和destroy方法是有默認(rèn)實(shí)現(xiàn)的盏缤,也就是我們不是必須重寫這兩個(gè)方法,但是doFilter 這個(gè)方法是一個(gè)核心的方法蓖扑,是我們必須要實(shí)現(xiàn)的唉铜。首先我們看init方法的作用,這個(gè)方法是用來(lái)初始化filter實(shí)例的律杠,也就是當(dāng)用戶請(qǐng)求了某個(gè)攔截器而此攔截器又匹配了某個(gè)過(guò)濾器潭流,此時(shí)web容器就會(huì)定位到該過(guò)濾器然后創(chuàng)建該filter類的實(shí)例對(duì)象并調(diào)用此實(shí)例的init方法竞惋,完成初始化工作。對(duì)于destroy方法毋庸置疑就是在過(guò)濾器執(zhí)行結(jié)束的時(shí)候調(diào)用灰嫉,主要完成對(duì)一些資源的釋放拆宛。下面主要看dofilter這個(gè)方法。
?????doFilter方法是filter接口中的核心方法讼撒,一旦創(chuàng)建完該過(guò)濾器的實(shí)例之后浑厚,會(huì)執(zhí)行dofilter方法,所有的過(guò)濾邏輯都是在此方法中進(jìn)行的根盒。主要有三個(gè)參數(shù)钳幅,第一個(gè)參數(shù)是一個(gè)ServletRequest對(duì)象,HttpServletRequest繼承于此接口炎滞,當(dāng)用戶請(qǐng)求某個(gè)攔截器的時(shí)候敢艰,檢測(cè)到此請(qǐng)求存在過(guò)濾器,于是會(huì)封裝好本次請(qǐng)求的相關(guān)數(shù)據(jù)册赛,傳遞給dofilter的ServletRequest參數(shù)钠导,ServletResponse參數(shù)的來(lái)源和ServletRequest是一樣的,都是由客戶端封裝過(guò)來(lái)的森瘪。至于第三個(gè)參數(shù)牡属,這是一個(gè)FilterChain處理鏈,詳細(xì)的介紹等說(shuō)明了web.xml中配置filter之后柜砾。
?????第一步如上湃望,創(chuàng)建一個(gè)繼承自filter接口的類,并實(shí)現(xiàn)其中的三個(gè)方法痰驱。第二步是在web.xml中配置該類用于過(guò)濾哪些攔截器证芭。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>
如上述的代碼,我們需要兩個(gè)操作担映,首先是定義一個(gè)filter废士,指定了該filter的name和相對(duì)應(yīng)的過(guò)濾器類。然后我們可以通過(guò)filter-mapping映射過(guò)濾器和URL蝇完,此處使用了兩個(gè)映射官硝,對(duì)該過(guò)濾器指定了對(duì)路徑名為/a和/b的請(qǐng)求進(jìn)行攔截。當(dāng)然這個(gè)url-pattern元素的值可以有以下三種形式短蜕,完全匹配(/a/b等)氢架,目錄匹配(/* 、 /abc/等)朋魔,擴(kuò)展名(.a岖研,*.b等)。
?????了解了配置filter的主要操作之后,我們回去看過(guò)濾器類孙援,我們說(shuō)init方法是在首次創(chuàng)建filter實(shí)例的時(shí)候害淤,用于執(zhí)行初始化操作的,其中有個(gè)參數(shù)FilterConfig 拓售,這是當(dāng)前filter的配置信息窥摄,其中方法如下:
String getFilterName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
其實(shí)在創(chuàng)建filter實(shí)例的時(shí)候,web容器會(huì)將此實(shí)例對(duì)應(yīng)在web.xml中的配置信息讀取并封裝成一個(gè)FilterConfig 對(duì)象傳遞給init方法础淤,getFilterName方法就是我們?cè)趙eb.xml中配置的filter-name崭放,getServletContext會(huì)獲取當(dāng)前servlet的上下文,當(dāng)我們?cè)诙xfilter的時(shí)候使用<init-param>來(lái)定義一些初始化參數(shù)的時(shí)候值骇,就可以使用此方法來(lái)獲取這些初始化參數(shù)莹菱。getInitParameterNames方法用于獲取所有初始化參數(shù)的枚舉集合。這樣我們?cè)趇nit方法中就可以獲取這些配置參數(shù)吱瘩,初始化filter實(shí)例。
?????上面我們只定義了一個(gè)filter迹缀,如果我們對(duì)于一次請(qǐng)求需要執(zhí)行多個(gè)filter使碾,進(jìn)行過(guò)濾操作的話,web容器會(huì)在你請(qǐng)求某個(gè)URL的時(shí)候祝懂,在web.xml中找到所有匹配的filter票摇,按照注冊(cè)的順序以FilterChain 鏈的形式傳遞到方法doFilter的第三個(gè)參數(shù)中,而這個(gè)filterChain中只有一個(gè)方法:
void doFilter(ServletRequest var1, ServletResponse var2)
如果我們?cè)?void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) 方法中調(diào)用var3.doFilter(var1,var2)砚蓬,就代表此filter實(shí)例結(jié)束矢门,則web服務(wù)器會(huì)檢查FilterChain對(duì)象中是否還有filter對(duì)象(因?yàn)檫@是一個(gè)鏈,filter的數(shù)量是大于等于一的)灰蛙,如果沒(méi)有就會(huì)放行祟剔,直接調(diào)用目標(biāo)地址,如果還有filter對(duì)象摩梧,就會(huì)轉(zhuǎn)而執(zhí)行下一個(gè)filter物延。正是由于這樣的機(jī)制,我們才可以對(duì)于一個(gè)URL請(qǐng)求添加多個(gè)filter過(guò)濾器仅父。
三叛薯、一個(gè)簡(jiǎn)單的實(shí)例
下面通過(guò)一個(gè)簡(jiǎn)單的實(shí)例直觀的感受下filter過(guò)濾器的作用:
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頁(yè)面</h1>
</body>
</html>
//set.jsp
//為了使程序簡(jiǎn)單,我們采用手動(dòng)設(shè)置session
//在實(shí)際的項(xiàng)目中笙纤,當(dāng)用戶登錄之后自動(dòng)設(shè)置session
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<%pageContext.getSession().setAttribute("state","0");%>
</body>
</html>
我們首先訪問(wèn)set.jsp頁(yè)面設(shè)置本次會(huì)話的session值耗溜,然后修改瀏覽器地址欄訪問(wèn)index.jsp頁(yè)面
敲下回車鍵,結(jié)果如下:
我們先看省容,敲下回車鍵之后抖拴,程序怎么執(zhí)行的,因?yàn)槲覀冊(cè)趙eb.xml中配置了MyFilter的攔截URL為index.jsp蓉冈,所以當(dāng)我們?cè)L問(wèn)index.jsp的時(shí)候城舞,會(huì)創(chuàng)建MyFilter 實(shí)例對(duì)象轩触,封裝配置信息到FilterConfig對(duì)象中,然后封裝request請(qǐng)求和response家夺,還有從web.xml 中讀取的FilterChain對(duì)象傳入MyFilter的doFilter方法中脱柱,我們?cè)谄渲蝎@取本次會(huì)話的session對(duì)象,取得其中的數(shù)據(jù)拉馋,如果為一放行榨为,否則跳轉(zhuǎn)到錯(cuò)誤頁(yè)面。此處的state的session值為0煌茴,我們?cè)趕et.jsp中設(shè)置的随闺,大家也可以在set.jsp頁(yè)面設(shè)置其值為1,這樣最終的結(jié)果會(huì)是這樣的:
上述的demo只是為了簡(jiǎn)單的演示蔓腐,其實(shí)使用filter可以完成大大的降低我們的代碼的冗余程度矩乐。這個(gè)例子是為了演示,所以很簡(jiǎn)單回论。
四散罕、Filter 的生命周期
?????當(dāng)用戶請(qǐng)求某個(gè)頁(yè)面時(shí)候,會(huì)到web.xml中匹配是否存在能夠匹配上此次請(qǐng)求的filter傀蓉,如果有封裝它的配置信息欧漱,F(xiàn)ilterChain鏈。然后調(diào)用init方法葬燎,完成初始化误甚,接著調(diào)用dofilter方法,處理核心邏輯谱净,當(dāng)此實(shí)例被銷毀的時(shí)候窑邦,會(huì)調(diào)用destroy方法。
本篇文章結(jié)束岳遥,如果有理解錯(cuò)誤奕翔,望大家指出!