Filter的基本功能是對Servlet容器調(diào)用Servlet的過程進(jìn)行攔截,從而在Servlet進(jìn)行響應(yīng)處理的前后實(shí)現(xiàn)一些特俗功能,例如,紀(jì)錄所有客戶端的每次訪問信息,同級靜態(tài)HTML頁面的訪問次數(shù),驗(yàn)證訪問者的身份,修改Servlet容器傳遞給Servlet的請求信息,修改Servlet回送給Servlet容器的響應(yīng)結(jié)果。
Filter的基本工作原理
Filter程序是一個實(shí)現(xiàn)了特殊接口(javax.servlet.Filter)的Java類,與Servlet程序相似,由Servlet容器進(jìn)行調(diào)用和執(zhí)行。
Filter程序需要在web.xml文件中進(jìn)行注冊和設(shè)置它所能攔截的資源,當(dāng)Servlet容器開始調(diào)用某個Servlet程序時,如果發(fā)現(xiàn)已經(jīng)注冊了一個Filter程序來對該Servlet進(jìn)行攔截,那么Servlet容器將不再直接調(diào)用Servlet的service方法,而是調(diào)用Filter的doFilter方法,再由doFilter方法決定是否去激活Servlet的service方法。在doFilter中調(diào)用FilterChain.doFilter方法來激活目標(biāo)Servlet的service方法。如果沒有調(diào)用,那么目標(biāo)Servlet的service的方法就不會執(zhí)行,在FilterChain.doFilter調(diào)用的前后增加一些處理代碼,就可以實(shí)現(xiàn)Servlet響應(yīng)前后的一些特殊處理。
Filter鏈
一個web程序中可以注冊多個Filter程序,每個Filter程序可以對一個或一組Servlet程序進(jìn)行攔截,如果有多個Filter程序?qū)δ硞€Servlet的程序的訪問過程進(jìn)行攔截,當(dāng)針對該Servlet的訪問請求到達(dá)時,這些攔截的FIlter程序就組成了一個Filter鏈,也稱過濾器鏈。這個鏈的攔截順序與他們在web.xml文件中的映射順序一致,上一個Filter中的doFilter方法調(diào)用的FilterChain.doFilter將激活下一個Filter的doFilter方法,最后一個Filter激活目標(biāo)Servlet的service方法。
Filter接口
public void init(FilterConfig filterConfig) throws ServletException;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public void destroy();
FilterConfig對應(yīng)web.xml中的配置:
<filter>
<filter-name>FirstFilter</filter-name>
<filter-class>FirstFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>gb2312</param-value>
</init-param>
</filter>
映射Filter
在web.xml文件中,一個<filter-mapping>元素用于設(shè)置一個Filter所負(fù)責(zé)攔截的資源。這可以通過兩種方式來指定:Servlet名稱和資源的訪問請求路徑。
Servlet容器調(diào)用一個資源的方式有以下四種:
- 通過正常的訪問請求調(diào)用
- 通過RequestDispatcher.include調(diào)用
- 通過RequestDispatcher.forward調(diào)用
- 作為錯誤響應(yīng)資源調(diào)用
<filter-mapping>
<filter-name>FirstFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>FirstFilter</filter-name>
<servlet-name>HelloServlet</servlet-name>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<url-pattern>和<servlet-name>是二選一的,<dispatcher>的值為上面所列出的4個。
如果一個Filter鏈中多次出現(xiàn)了同一個Filter程序,這個Filter程序的攔截處理過程將被執(zhí)行多次
Filter的運(yùn)行過程分析
Web服務(wù)器在啟動加載Web應(yīng)用程序時,將根據(jù)該Web應(yīng)用程序的部署描述文件(web.xml)中的設(shè)置,為其中的每個<filter>元素創(chuàng)建一個相應(yīng)的Filter實(shí)例對象,F(xiàn)ilter實(shí)例對象被創(chuàng)建后,將一直駐留在內(nèi)存中,直到Web應(yīng)用程序卸載時才隨之卸載。
Web服務(wù)器啟動后,瀏覽器根據(jù)用戶的請求生成HTTP請求消息,并將其發(fā)送給Web服務(wù)器。
Web服務(wù)器檢查內(nèi)存中是否存在負(fù)責(zé)處理當(dāng)前請求的Servlet程序的實(shí)例,如果不存在,則加載和創(chuàng)建該Servlet對象。注意:Filter程序不會攔截Web服務(wù)器創(chuàng)建Servlet實(shí)例對象的過程。
Web服務(wù)器創(chuàng)建針對該次訪問的請求對象和響應(yīng)對象。請求對象中包含了HTTP的請求消息,響應(yīng)對象用于封裝將要發(fā)送的HTTP響應(yīng)消息,響應(yīng)消息的初始內(nèi)容為空,但是以后可以調(diào)用它的各種方法來生成HTTP響應(yīng)消息的各個部分。
如果存在可以攔截當(dāng)前訪問請求的Filter設(shè)置,Web容器將把這些Filter組裝成一個攔截這次訪問請求的Filter鏈,然后調(diào)用Filter鏈中的第一個Filter對象的doFilter方法,并將請求對象和響應(yīng)對象傳遞給該方法。
Filter對象的doFilter方法的前置代碼從請求對象中讀取請求信息,這時也可以在響應(yīng)對象中寫入部分響應(yīng)頭和響應(yīng)實(shí)體內(nèi)容。
Filter對象的doFilter方法調(diào)用FilterChain.doFilter方法,通知Web容器把請求交給Filter鏈中的下一個Filter去處理,容器將以
filterChain.doFilter(servletRequest, servletResponse);接收的參數(shù)作為傳遞給下一個Filter的請求和響應(yīng)對象。假定Filter鏈中只有一個Filter,容器將把請求交給目標(biāo)Servlet程序去處理,即Web容器調(diào)用目標(biāo)Servlet的service方法。
目標(biāo)Servlet對象的service方法從請求消息中讀取請求信息,并向響應(yīng)對象中寫入響應(yīng)頭和響應(yīng)體信息。
目標(biāo)Servlet對象的service方法執(zhí)行完畢返回。
目標(biāo)Servlet響應(yīng)完成后,在Filter對象的doFilter方法調(diào)用FilterChain.doFilter方法返回。
Filter對象的doFilte方法繼續(xù)執(zhí)行其中的調(diào)用FilterChain.doFilter方法的語句后面的代碼,這些后置代碼仍然可以從請求對象中讀取請求信息,也可以向響應(yīng)對象中寫入部分響應(yīng)頭和響應(yīng)實(shí)體數(shù)據(jù),執(zhí)行完畢后返回。
Web服務(wù)器從響應(yīng)對象中讀取響應(yīng)信息。
Web服務(wù)器將響應(yīng)信息發(fā)送給瀏覽器處理和顯示,一次請求響應(yīng)過程完全結(jié)束,request和response變成垃圾,等待垃圾回收器將其徹底從內(nèi)存中清除。
用Filter實(shí)現(xiàn)對資源的集中訪問保護(hù)
如果一個網(wǎng)站的所有頁面都是靜態(tài)頁面,但是我們又需要用戶登錄以查看,怎么辦呢,如果是jsp頁面,我們可以直接判斷登錄進(jìn)行轉(zhuǎn)發(fā),如果是純靜態(tài)的話,我們就可以使用Filte了,在過濾器中進(jìn)行登錄判斷和轉(zhuǎn)發(fā)到登錄頁面的處理。
在Filter程序中修改請求和響應(yīng)消息
- 修改Servlet容器傳遞給Servlet的請求信息
- 修改Servlet回送給Servlet容器的響應(yīng)結(jié)果
要實(shí)現(xiàn)上述需求可以在doFilter中使用修飾后的request和response,使用裝飾模式繼承HttpServletRequestWrapper和HttpServletResponseWrapper。
用Filter實(shí)現(xiàn)文件上傳請求的透明處理
我們知道,對于采用POST請求方式提交的multipart/form-data類型的HTTP請求消息,在Servlet程序中無法調(diào)用HttpServletResponse實(shí)例對象的getParameter等方法來讀取表單字段元素的信息。對于這種情況可以通過一個Filter程序進(jìn)行預(yù)處理,讓自定義的請求對象的getParameter等方法可以反悔multipart/form-data編碼類型的HTTP請求消息中的表單字段元素信息,從而讓目標(biāo)Servlet能夠透明地使用request對象的getParameter方法。
用Filter實(shí)現(xiàn)響應(yīng)正文的壓縮
HTTP協(xié)議支持響應(yīng)消息的實(shí)體內(nèi)容進(jìn)行壓縮后再進(jìn)行傳遞,以便減少網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)量,從而提高網(wǎng)絡(luò)的傳輸效率。壓縮的處理可以在Servlet程序中,當(dāng)然最好是在Filter程序中。
HTTP協(xié)議中最常見的壓縮方式是gzip和compress兩種格式,如果瀏覽器程序支持對響應(yīng)消息的壓縮,它在請求消息中需要使用一個Accept-Encoding頭字段來指明其能夠解碼的數(shù)據(jù)壓縮格式。如果Web服務(wù)器對響應(yīng)消息中的實(shí)體內(nèi)容進(jìn)行了壓縮,它需要使用Content-Encoding頭字段來指明壓縮方式,瀏覽器將采用Content-Encoding頭字段中指定的壓縮方式對接收到的實(shí)體內(nèi)容進(jìn)行解壓后顯示。