注意Filter雖然很常用,但是覆蓋的范圍太廣,這里我們只介紹設置編碼和控制權限的過濾器,其他的使用方式還需要大家自行積累。 如果你不滿足以下任一條件,請繼續(xù)閱讀,否則請?zhí)^此后的部分,進入下一章:第 8 章 配置listener監(jiān)聽器。
編碼問題會不會成為中國人學java的標志呢? 通過之前的討論第 2.2.2 節(jié) “POST亂碼”,我們知道為了避免提交數(shù)據(jù)的亂碼問題,需要在每次使用請求之前設置編碼格式。在你復制粘貼了無數(shù)次request.setCharacterEncoding("gb2312");后,有沒有想要一勞永逸的方法呢?能不能一次性修改所有請求的編碼呢? 用Filter吧,它的名字是過濾器,可以批量攔截修改servlet的請求和響應。 我們編寫一個EncodingFilter.java,來批量設置請求編碼。 package anni; 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 EncodingFilter implements Filter { public void init(FilterConfig config) throws ServletException {} public void destroy() {} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("gb2312"); chain.doFilter(request, response); } } 在此EncodingFilter實現(xiàn)了Filter接口,F(xiàn)ilter接口中定義的三個方法都要在EncodingFilter中實現(xiàn),其中doFilter()的代碼實現(xiàn)主要的功能:為請求設置gb2312編碼并執(zhí)行chain.doFilter()繼續(xù)下面的操作。 與servlet相似,為了讓filter發(fā)揮作用還需要在web.xml進行配置。 <filter> <filter-name>EncodingFilter</filter-name> <filter-class>anni.EncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> filter標簽部分定義使用的過濾器,filter-mapping標簽告訴服務器把哪些請求交給過濾器處理。這里的/*表示所有請求,/表示根路徑,*(星號)代表所有請求,加在一起就變成了根路徑下的所有請求。 這樣,所有的請求都會先被EncodingFilter攔截,并在請求里設置上指定的gb2312編碼。 例子在lingo-sample/07-01目錄下,這次我們不需要在test.jsp中為請求設置編碼也可以得到正常的中文參數(shù)了,EncodingFilter圓滿的完成了它的工作。 出于信息安全和其他一些原因的考慮,項目中的一些頁面要求用戶滿足了一定條件之后才能訪問。比如,讓用戶輸入賬號和密碼,如果輸入的信息正確就在session里做一個成功登錄的標記,其后在請求保密信息的時候判斷session中是否有已經(jīng)登錄成功的標記,存在則可以訪問,不存在則禁止訪問。 如07-02例子中所示,進入首頁看到的就是登錄頁面。 ![]() 現(xiàn)在用戶還沒有登錄,如果直接訪問保密信息,就會顯示無法訪問保密信息的頁面,并提醒用戶進行注冊。 ![]() 返回登錄頁面后,輸入正確的用戶名和密碼,點擊登錄。 ![]() 后臺程序判斷用戶名和密碼正確無誤后,在session中設置已登錄的標記,然后跳轉到保密信息頁面。 ![]() 我們要保護的頁面是admin/index.jsp,為此我們在web.xml進行如下配置。 <filter> <filter-name>SecurityFilter</filter-name> <filter-class>anni.SecurityFilter</filter-class> </filter> <filter-mapping> <filter-name>SecurityFilter</filter-name> <url-pattern>/admin/*</url-pattern> </filter-mapping> 定義SecurityFilter過濾器,讓它過濾匹配/admin/*的所有請求,這就是說,對/admin/路徑下的所有請求都會接受SecurityFilter的檢查,那么SecurityFilter里到底做了些什么呢? public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; HttpSession session = req.getSession(); if (session.getAttribute("username") != null) { chain.doFilter(request, response); } else { res.sendRedirect("../failure.jsp"); } } 首先要將ServletRequest和ServletResponse轉換成HttpServletRequest和HttpServletResponse,因為Filter本來設計成為多種協(xié)議服務,http協(xié)議僅僅是其中一部分。不過我們接觸到的也只有http,而且也只有轉換成對應HttpServletRequest和HttpServletResponse才能進行下面的session操作和頁面重定向。 得到了http請求之后,可以獲得請求對應的session,判斷session中的username變量是否為null,如果不為null,說明用戶已經(jīng)登錄,就可以調(diào)用doFilter繼續(xù)請求訪問的資源。如果為null,說明用戶還沒有登錄,禁止用戶訪問,并使用頁面重定向跳轉到failure.jsp頁面顯示提示信息。 session中的username實在登錄的時候設置進去的,值就是登錄用戶使用的用戶名,詳細代碼可以參考07-02/WEB-INF/src/LoginServlet.java,登錄和注銷都寫成了servlet并映射到/login.do和/logout.do這兩個請求路徑上。源代碼和web.xml配置請自行參考07-02中的例子,這里就不復述了。 我們再來看看頁面重定向的寫法,res.sendRedirect()中使用的是"../failure.jsp",兩個點(..)代表當前路徑的上一級路徑,這是因為SecurityFilter負責處理的是/admin/下的請求,而/failure.jsp的位置在/admin/目錄的上一級,所以加上兩個點才能正確跳轉到failure.jsp。當然這里使用forward()也可以,但是要注意在不同路徑下做請求轉發(fā)會影響頁面中相對路徑的指向。相關討論在:第 3.4.2 節(jié) “forward導致找不到圖片”。 filter-mapping和servlet-mapping都是將對應的filter或servlet映射到某個url-pattern上,當客戶發(fā)起某一請求時,服務器先將此請求與web.xml中定義的所有url-pattern進行匹配,然后執(zhí)行匹配通過的filter和servlet。 你可以使用三種方式定義url-pattern。
現(xiàn)在咱們也發(fā)現(xiàn)java的請求映射有多傻了,靈活配置根本是不可能的任務。 想要獲得所有以user開頭.do結尾的請求嗎?user*.do在url-pattern是無法識別的,只能配置成*.do,再去servlet中對請求進行篩選。 想要讓一個servlet負責多個請求嗎?/user/*,/admin/*,*.do寫在一起url-pattern也不認識,只能配成多個servlet-mapping。 <servlet-mapping> <servlet-name>ControllerServlet</servlet-name> <url-pattern>/user/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ControllerServlet</servlet-name> <url-pattern>/admin/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ControllerServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> java的復雜性在此處顯露無疑。實際使用時,最好不要依賴web.xml中的配置,在自己的類中實現(xiàn)靈活配置才是正途。 其實在07-02這個例子里,我們使用了兩個過濾器,EncodingFilter負責設置編碼,SecurityFilter負責控制權限,那這兩個過濾器是怎么起作用的呢?它們兩個同時過濾一個請求時誰先誰后呢? 下面這個圖會告訴我們答案。 ![]() 所有的奧秘就在Filter中的FilterChain中。服務器會按照web.xml中過濾器定義的先后循序組裝成一條鏈,然后一次執(zhí)行其中的doFilter()方法。執(zhí)行的順序就如上圖所示,執(zhí)行第一個過濾器的chain.doFilter()之前的代碼,第二個過濾器的chain.doFilter()之前的代碼,請求的資源,第二個過濾器的chain.doFilter()之后的代碼,第一個過濾器的chain.doFilter()之后的代碼,最后返回響應。 因此在07-02中執(zhí)行的代碼順序是:
過濾鏈的好處是,執(zhí)行過程中任何時候都可以打斷,只要不執(zhí)行chain.doFilter()就不會再執(zhí)行后面的過濾器和請求的內(nèi)容。而在實際使用時,就要特別注意過濾鏈的執(zhí)行順序問題,像EncodingFilter就一定要放在所有Filter之前,這樣才能確保在使用請求中的數(shù)據(jù)前設置正確的編碼。 我們已經(jīng)了解了filter的基本用法,還有一些細節(jié)配置在特殊情況下起作用。 在servlet-2.3中,F(xiàn)ilter會過濾一切請求,包括服務器內(nèi)部使用forward轉發(fā)請求和<%@ include file="/index.jsp"%>的情況。 到了servlet-2.4中Filter默認下只攔截外部提交的請求,forward和include這些內(nèi)部轉發(fā)都不會被過濾,但是有時候我們需要forward的時候也用到Filter,這樣就需要如下配置。 <filter> <filter-name>TestFilter</filtername> <filter-class>anni.TestFilter</filter-class> </filter> <filter-mapping> <filter-name>TestFilter</filtername> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>EXCEPTION</dispatcher> </filter-mapping> 這樣TestFilter就會過濾所有狀態(tài)下的請求。如果我們沒有進行設置,默認使用的就是REQUEST。而EXCEPTION是在isErrorPage="true"的情況下出現(xiàn)的,這個用處不多,看一下即可。 這里FORWARD是解決request.getDispatcher("index.jsp").forward(request, response);無法觸發(fā)Filter的關鍵,配置上這個以后再進行forward的時候就可以觸發(fā)過濾器了。 Filter還有一個有趣的用法,在filter-mapping中我們可以直接指定servlet-mapping,讓過濾器只處理一個定義在web.xml中的servlet。 <filter-mapping> <filter-name>TestFilter</filter-name> <servlet-name>TestServlet</servlet-name> </filter-mapping> <servlet> <servlet-name>TestServlet</servlet-name> <servlet-class>anni.TestServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>TestServlet</servlet-name> <url-pattern>/TestServlet</url-pattern> </servlet-mapping> |
|