提出問題
1、我們在訪問后台很多頁面時都需要登錄,只有登錄的用戶才能查看這些頁面,我們需要 在每次請求的時候都檢查用戶是否登陸,這樣做很麻煩,有沒有一種方法可以在我們請求之 前就幫我們做這些事情。有!
2、我們 web 應用經常會接收中文字符,由於可能導致中文亂碼,我們每次都需要在方法的開始使用 request.setCharacterEncoding(“utf-8”);能不能在我們要獲取參數值直接就可以自己設置好編碼呀。能!
這種問題的解決方法我們想到了一種辦法。那就是在每次請求之前我們先將它攔截起來,當 我們設置好一切東西的時候,再將請求放行。類似與我們地鐵站的檢票系統。每個人進站的 時候必須刷卡,扣完錢后才可以進站坐車。
web 中也有這個機制,我們叫做過濾器。就是我們接下來學習的 filter
Filter 簡介
什么是 filter
1) Filter(過濾器) 的基本功能是對 Servlet 容器調用 Servlet (JSP)的過程進行攔截, 從而在 Servlet 處理請求前和 Servlet 響應請求后實現一些特殊的功能。
2) 在 Servlet API 中定義了三個接口類來開供開發人員編寫 Filter 程序: Filter,FilterChain, FilterConfig
3) Filter 程序是一個實現了 Filter 接口的 Java 類,與 Servlet 程序相似,它由 Servlet容器進行調用和執行
4) Filter 程序需要在 web.xml 文件中進行注冊和設置它所能攔截的資源:Filter 程序可以攔截 Jsp, Servlet, 靜態圖片文件和靜態 html 文件
filter 的運行原理是什么
這個 Servlet 過濾器就是我們的 filter
1)當在 web.xml 中注冊了一個 Filter 來對某個 Servlet 程序進行攔截處理時,這個Filter 就成了 Tomcat 與該 Servlet 程序的通信線路上的一道關卡,該 Filter 可以對Servlet 容器發送給 Servlet 程序的請求和 Servlet 程序回送給 Servlet 容器的響應進行攔截,可以決定是否將請求繼續傳遞給 Servlet 程序,以及對請求和相應信息是否進行修改
2)在一個 web 應用程序中可以注冊多個 Filter 程序,每個 Filter 程序都可以對一個或一組 Servlet 程序進行攔截。
3)若有多個 Filter 程序對某個 Servlet 程序的訪問過程進行攔截,當針對該 Servlet 的訪問請求到達時,web 容器將把這多個 Filter 程序組合成一個 Filter 鏈(過濾器鏈)。Filter 鏈中各個 Filter 的攔截順序與它們在應用程序的 web.xml 中映射的順序一致
Filter-helloword
Hello-World
filter 編寫三步驟:
1、創建 filter 實現類,實現 filter 接口
2、編寫 web.xml 配置文件,配置 filter 的信息
3、運行項目,可以看到 filter 起作用了
代碼:
//1、filter 實現類 public class MyFirstFilter implements Filter{ @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("初始化方法"); } @Override public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException { System.out.println("dofilter方法"); } @Override public void destroy() { System.out.println("銷毀方法..."); } } //2、web.xml 配置 <filter> <filter-name>MyFirstFilter</filter-name> <filter-class>com.atguigu.filter.MyFirstFilter</filter-class> </filter> <filter-mapping> <filter-name>MyFirstFilter</filter-name> <url-pattern>/index.jsp</url-pattern> </filter-mapping> //3、運行程序,發現 index.jsp 頁面不顯示了,后台輸出“dofilter 方法”,說明我們寫的 filter 執行了。
filter 的生命周期
1)在服務器啟動時,filter 被創建並初始化,執行 init()方法。
2)請求通過 filter 時執行 doFilter 方法。
3)服務器停止時,調用 destroy 方法。
filter 放行請求
我們發現,剛才的 filter 配置好后,index.jsp 頁面沒法訪問了,訪問這個頁面的時候 filter的 dofilter 方法被調用了。說明 dofilter 這個方法攔截了我們的請求。
我們如何顯示頁面呢。也就是如何將請求放行呢。我們觀察發現有個 filterChain 被傳入到這個方法里面了。filterChain 里面有個 doFilter()方法。放行請求只需要調用 filterChain 的 dofilter 方法。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,ServletException { System.out.println("dofilter方法"); chain.doFilter(request, response);//放行請求 }
filter 攔截原理
我們在 chain.doFilter(request, response);方法后也寫一句話,System.out.println
(“doFilter 方法執行后…”),在 index.jsp 頁面也寫上 jsp 腳本片段,輸出我是 jsp 頁面。運行程序發現控制台輸出了這幾句話:
dofilter 方法… 我是 jsp 頁面
dofilter 方法后…
我們不難發現 filter 的運行流程
FilterChain
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
在 doFilter 執行之前,由容器將 filterChain 對象傳入方法。調用此對象的.doFilter()方法可以將請求放行,實際上是執行過濾器鏈中的下一個 doFilter 方法,但是如果只有一個過濾器,則為放行。
FilterConfig
FilterConfig 類似 ServletConfig,是 filter 的配置信息對象。FilterConfig 對象具有以下方法。
getFilterName():獲取當前 filter 的名字。獲取的是在 web.xml 中配置的 filter-name 的值
getInitParameter(String name):獲取 filter 的初始化參數。在 web.xml 中配置
getInitParameterNames():獲取 filter 初始化參數名的集合。
getServletContext():獲取當前 web 工程的 ServletContext 對象。
Filter 的 url-pattern
url-pattern 是配置 filter 過濾哪些請求的。主要有以下幾種配置:
web.xml 中配置的/都是以當前項目路徑為根路徑的
1)精確匹配:
/index.jsp/user/login 會在請求/index.jsp、/user/login 的時候執行過濾方法
2)路徑匹配:
/user/* /* 凡是路徑為/user/下的所有請求都會被攔截,/*表示攔截系統的所有請求,包括靜態資源文件。
3)擴展匹配:
*.jsp *.action 凡是后綴名為.jsp .action 的請求都會被攔截。
注意:/login/*.jsp 這種寫法是錯誤的,只能是上述三種的任意一種形式。不能組合新形式。
*jsp 也是錯誤的,擴展匹配必須是后綴名
4)多重 url-pattern 配置
上面的三種形式比較有局限性,但是 url-pattern 可以配置多個,這樣這三種組合基本就能解決所有問題了
多 Filter 執行順序
如果同一個資源有多個 filter 都對其攔截,則攔截的順序是按照 web.xml 中配置的順序進行的
執行流程圖如下
請求總是在處理之后再回來執行 doFilter 之后的方法。
HttpServletWrapper 和 HttpServletResponseWrapper
定義
Servlet API 中提供了一個 HttpServletRequestWrapper 類來包裝原始的 request 對象,HttpServletRequestWrapper 類實現了 HttpServletRequest 接口中的所有方法, 這些方法的內部實現都是僅僅調用了一下所包裝的的 request 對象的對應方法
//包裝類實現 ServletRequest 接口. public class ServletRequestWrapper implements ServletRequest { //被包裝的那個 ServletRequest 對象 private ServletRequest request; //構造器傳入 ServletRequest 實現類對象 public ServletRequestWrapper(ServletRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } this.request = request; } //具體實現 ServletRequest 的方法: 調用被包裝的那個成員變量的方法實現。 public Object getAttribute(String name) { return this.request.getAttribute(name); } public Enumeration getAttributeNames() { return this.request.getAttributeNames(); } //... }
相類似 Servlet API 也提供了一個 HttpServletResponseWrapper 類來包裝原始的 response 對象
作用
用於對 HttpServletRequest 或 HttpServletResponse 的某一個方法進行修改或增強.
public class MyHttpServletRequest extends HttpServletRequestWrapper{ public MyHttpServletRequest(HttpServletRequest request) { super(request); } @Override public String getParameter(String name) { String val = super.getParameter(name); if(val != null && val.contains(" fuck ")){ val = val.replace("fuck", "****"); } return val; } }
使用
在 Filter 中, 利用 MyHttpServletRequest 替換傳入的 HttpServletRequest
HttpServletRequest req = new MyHttpServletRequest(request); filterChain.doFilter(req, response);
此時到達目標 Servlet 或 JSP 的 HttpServletRequest 實際上是 MyHttpServletRequest