Filter
一、概念
Javaweb三大組件(Servlet、Filter、Listener)之一,Filter就是過濾器,當訪問服務器資源時,Filter可以將請求攔截下來,完成一些特定的功能,也就是過濾特定的請求資源、請求信息、響應信息;當一個請求到來,Web服務器首先判斷是否有過濾器與請求資源相關聯,如果有那么將請求交給過濾器處理,再由過濾器決定是否交給請求資源,響應則相反。過濾器一般用於完成通用的操作,比如:登錄驗證、統一編碼處理、敏感字符過濾……
過濾器的基本原理:
- Filter就是一個實現了接口Filter的java類,與Servlet類似的由Tomcat執行
- 當為一個Filter配置了攔截資源,當請求該資源時,可以由該Filter決定是否放行該請求到請求的資源,以及決定是否對請求消息、響應消息作出修改
- 當 Servlet 容器開始調用某個 Servlet 程序時,如果發現已經注冊了一個 Filter 程序來對該 Servlet 進行攔截,那么容器不再直接調用 Servlet 的 service 方法,而是調用 Filter 的 doFilter 方法,再由 doFilter 方法決定是否去激活 service 方法。
- 但在 Filter.doFilter 方法中不能直接調用 Servlet 的 service 方法,而是調用 FilterChain.doFilter 方法來激活目標 Servlet 的 service 方法,FilterChain 對象時通過 Filter.doFilter 方法的參數傳遞進來的。
- 只要在 Filter.doFilter 方法中調用 FilterChain.doFilter 方法的語句前后增加某些程序代碼,這樣就可以在 Servlet 進行響應前后實現某些特殊功能。
- 如果在 Filter.doFilter 方法中沒有調用 FilterChain.doFilter 方法,則目標 Servlet 的 service 方法不會被執行,這樣通過 Filter 就可以阻止某些非法的訪問請求。
二、編寫Filter的步驟
-
定義一個類,實現接口
Filter -
復寫接口的方法
-
配置攔截路徑(訪問什么資源,過濾器會生效)
@WebFilter("/*")// /*表示訪問所有資源之前都會執行該過濾器,如果是/demo.jsp就是表示訪問demo.jsp之前執行該過濾器 public class FilterDemo1 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } // doFilter業務處理的核心代碼區,相當於Servlet的service方法 @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("FilterDemo1----------------------"); filterChain.doFilter(servletRequest, servletResponse);// 過濾器放行請求,可以訪問到index.jsp } @Override public void destroy() { } }
三、過濾器的一些細節
1. web.xml配置
<!--先為FilterDemo1這個類配置一個過濾器的名字demo1,然后配置該過濾器應用的資源范圍-->
<filter>
<filter-name>demo1</filter-name>
<filter-class>cn.zhuobo.web.filter.FilterDemo1</filter-class>
</filter>
<filter-mapping>
<filter-name>demo1</filter-name>
<url-pattern>/*</url-pattern> <!-- 這里配置的是攔截路徑 -->
</filter-mapping>
2. 過濾器的執行流程
-
執行過濾器
-
執行過濾器放行后的請求資源
-
執行過濾器放行后的代碼
// 對request對象做一些增強 System.out.println("filter11111111111111111"); // 放行 chain.doFilter(req, resp); //回程,對response對象做一些增強 System.out.println("filter222222222222222222");
3. 過濾器的生命周期方法
- init:服務器啟動后創建Filter對象,調用init方法,init方法只執行一次,一般用來加載資源
- doFilter:每次請求被攔截的資源時都會執行,可以執行多次
- destroy:服務器關閉后Filter對象被銷毀,如果服務器是正常關閉,就會執行destroy方法
4. 過濾器配置細節
-
攔截路徑的配置
- 攔截具體資源:index.jsp,表示只有訪問該資源時對應的過濾器才會被執行
- 攔截目錄:/dir/*,表示訪問dir目錄下的所有資源過濾器都會被執行
- 攔截后綴名:*.jsp,表示訪問jsp資源時過濾器會被執行
- 攔截全部資源:/*,表示訪問所有資源都會執行過濾器
-
攔截方式的配置(資源訪問的方式,比如瀏覽器直接請求,轉發請求),使用dispatcherTypes屬性配置,下面為該屬性的5個值:
- DispatcherType.REQUEST:默認值,只有直接請求該資源才會執行過濾器
- DispatcherType.FORWARD:轉發訪問該資源才執行過濾器
- DispatcherType.INCLUDE:包含訪問資源
- DispatcherType.ERROR:錯誤跳轉資源
- DispatcherType.ASYNC:異步訪問該資源
注意dispatcherTypes的值可以是一個數組,也就是可以配置多個值,這種情況下,無論是直接訪問,還是請求轉發都會執行過濾器,配置了兩個值
@WebFilter(value = "/index.jsp", dispatcherTypes = {DispatcherType.FORWARD, DispatcherType.REQUEST}) // 直接訪問或者轉發
5. 過濾器鏈(配置多個過濾器)
可以配置多個過濾器,而且可以都生效,但是過濾器有執行順序的問題,上一個的doFilter方法激活下一個的doFilter方法,最后一個doFilter方法激活訪問Servlet的service方法,過濾器鏈的中間任意一個filter沒有調用doFilter方法,那么最終的Servlet的service方法都不會被執行:如果有兩個過濾器分別是Filter1和Filter2,那么執行順序如下:
- Filter1執行
- Filter2執行
- 被請求的資源執行
- Filter2執行
- Filter1執行
還有就是過濾器的先后問題:
- 注解配置方式:按照類名的字符串比較順序,值小的先執行
- web.xml配置方式:< filter-mapping >標簽先定義者先執行
四、過濾器的應用(登錄驗證、敏感詞過濾)
1. 登錄驗證
-
登錄驗證需求:
- 訪問資源,驗證是否已經登錄
- 如果登錄了就直接放行
- 否則就直接跳轉發到登錄頁面,提示“請您先登錄”
-
登錄驗證的過濾器的核心邏輯:
-
判斷請求的資源是否登錄相關的資源(登錄頁面相關的資源),如果是,就直接放行;否則就要判斷是否已經登錄
-
判斷是否已經登錄,因為如果登錄了都會在session中存儲user鍵,判斷session中是否有user即可判斷是否已經登錄;
- 如果已經登錄,直接放行
- 否則跳轉到登錄頁面,提示“請您先登錄”
-
具體代碼實現:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { // 將req強制轉換為HttpServletRequest,因為一般都是HTTP協議的 HttpServletRequest request = (HttpServletRequest) req; //1. 獲取訪問資源的URI,就是那個很長的 String uri = request.getRequestURI(); // 2. 判斷uri是否包含有登錄相關的資源,包括login.jsp,相關的驗證碼、css樣式、js、字體樣式等 if(uri.contains("/login.jsp") || uri.contains("/loginServlet") || uri.contains("checkCodeServlet") || uri.contains("/css/")|| uri.contains("/js/") || uri.contains("/fonts/")) { // 含有登錄相關的資源,表示用戶就是要去登錄的,直接放行 chain.doFilter(req, resp); } else { // 否則就判斷使用是否已經登錄了 Object user = request.getSession().getAttribute("user"); if (user != null) { // 已經登錄的,就直接放行 chain.doFilter(req, resp); } else { // 否則就跳轉到登錄頁面,並且提示要先登錄 request.setAttribute("login_msg", "您還沒有登錄,請您先登錄!"); request.getRequestDispatcher("/login.jsp").forward(request, resp); } } }
-
2. 敏感詞匯過濾
-
敏感詞匯過濾需求:
- 對錄入數據進行敏感詞匯過濾
- 敏感詞匯參考《敏感詞匯.txt》
- 將敏感詞替換為***
-
分析:
- 請求參數是封裝在request對象的
- Filter過濾器doFilter的參數req對象事實上和封裝請求參數的request對象時同一個
- 在doFilter中將請求參數中的敏感詞匯替換為***,重新封裝request對象
- 放行,chain.doFilter的參數傳入重新封裝的request對象
-
增強對象的功能,采用設計模式中的裝飾模式、代理模式可增強對象的功能
-
裝飾模式
-
代理模式:代理對象代理真實對象,達到增強真實對象功能的模式
- 真實對象:被代理的對象
- 代理對象:
實現方式:
- 靜態代理:有一個類文件描述代理模式
- 動態代理:在內存中形成代理
-
動態代理的實現步驟
- 代理對象和真實對象實現相同的接口(代理對象和真實對象時兄弟);
- 代理對象 = Proxy.newProxyInstance();
- 使用代理對象調動方法;
- 增強方法。
-
增強方法的方式 :
- 增強參數列表
- 增強返回值類型
- 增強方法體執行邏輯
public class ProxyTest { public static void main(String[] args) { Huawei huawei = new Huawei(); /** *Proxy.newProxyInstance方法的三個參數: * 1. 真實對象的類加載器:真實對象.getClass().getClassLoader() * 2. 接口數組:代理對象實現的接口,真實對象.getClass().getClassLoader() * 3. 處理器:new InvocationHandler() */ SaleComputer proxy_huawei = (SaleComputer) Proxy.newProxyInstance(huawei.getClass().getClassLoader(), huawei.getClass().getInterfaces(), new InvocationHandler() { /** *invoke方法是代理邏輯編寫的方法,代理對象調用的所有方法都會觸發該方法的執行 * * @param proxy:代理對象 * @param method:代理對象調用的方法,被封裝成的對象 * @param args:代理對象調用方法時,傳遞的參數封裝的數組 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 判斷執行的方法是不是sale方法,如果是就增強參數(修改參數) if("sale".equals(method.getName())) { double money = (double) args[0]; money = money * 0.8;// 改變參數的值 // 使用真實對象調用方法 String obj = (String) method.invoke(huawei, money); // 增強返回值 return obj + "、電腦包、鼠標墊......"; } else {// 如果不是sale方法,那么就原樣執行 // 使用真實對象調用sale方法 Object obj = method.invoke(huawei, args); return obj; } } }); // 代理對象調用sale方法 String computer = proxy_huawei.sale(7788); System.out.println(computer); proxy_huawei.show(); } }
-
