Filter過濾器


Filter

一、概念

Javaweb三大組件(Servlet、Filter、Listener)之一,Filter就是過濾器,當訪問服務器資源時,Filter可以將請求攔截下來,完成一些特定的功能,也就是過濾特定的請求資源、請求信息、響應信息;當一個請求到來,Web服務器首先判斷是否有過濾器與請求資源相關聯,如果有那么將請求交給過濾器處理,再由過濾器決定是否交給請求資源,響應則相反。過濾器一般用於完成通用的操作,比如:登錄驗證、統一編碼處理、敏感字符過濾……

過濾器的基本原理:

  1. Filter就是一個實現了接口Filter的java類,與Servlet類似的由Tomcat執行
  2. 當為一個Filter配置了攔截資源,當請求該資源時,可以由該Filter決定是否放行該請求到請求的資源,以及決定是否對請求消息、響應消息作出修改
  3. 當 Servlet 容器開始調用某個 Servlet 程序時,如果發現已經注冊了一個 Filter 程序來對該 Servlet 進行攔截,那么容器不再直接調用 Servlet 的 service 方法,而是調用 Filter 的 doFilter 方法,再由 doFilter 方法決定是否去激活 service 方法。
  4. 但在 Filter.doFilter 方法中不能直接調用 Servlet 的 service 方法,而是調用 FilterChain.doFilter 方法來激活目標 Servlet 的 service 方法,FilterChain 對象時通過 Filter.doFilter 方法的參數傳遞進來的。
  5. 只要在 Filter.doFilter 方法中調用 FilterChain.doFilter 方法的語句前后增加某些程序代碼,這樣就可以在 Servlet 進行響應前后實現某些特殊功能。
  6. 如果在 Filter.doFilter 方法中沒有調用 FilterChain.doFilter 方法,則目標 Servlet 的 service 方法不會被執行,這樣通過 Filter 就可以阻止某些非法的訪問請求。

二、編寫Filter的步驟

  1. 定義一個類,實現接口 Filter

  2. 復寫接口的方法

  3. 配置攔截路徑(訪問什么資源,過濾器會生效)

    @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. 過濾器的執行流程

  1. 執行過濾器

  2. 執行過濾器放行后的請求資源

  3. 執行過濾器放行后的代碼

    // 對request對象做一些增強
    System.out.println("filter11111111111111111");
    
    // 放行
    chain.doFilter(req, resp);
    
    //回程,對response對象做一些增強
    System.out.println("filter222222222222222222");
    

3. 過濾器的生命周期方法

  1. init:服務器啟動后創建Filter對象,調用init方法,init方法只執行一次,一般用來加載資源
  2. doFilter:每次請求被攔截的資源時都會執行,可以執行多次
  3. destroy:服務器關閉后Filter對象被銷毀,如果服務器是正常關閉,就會執行destroy方法

4. 過濾器配置細節

  1. 攔截路徑的配置

    1. 攔截具體資源:index.jsp,表示只有訪問該資源時對應的過濾器才會被執行
    2. 攔截目錄:/dir/*,表示訪問dir目錄下的所有資源過濾器都會被執行
    3. 攔截后綴名:*.jsp,表示訪問jsp資源時過濾器會被執行
    4. 攔截全部資源:/*,表示訪問所有資源都會執行過濾器
  2. 攔截方式的配置(資源訪問的方式,比如瀏覽器直接請求,轉發請求),使用dispatcherTypes屬性配置,下面為該屬性的5個值:

    1. DispatcherType.REQUEST:默認值,只有直接請求該資源才會執行過濾器
    2. DispatcherType.FORWARD:轉發訪問該資源才執行過濾器
    3. DispatcherType.INCLUDE:包含訪問資源
    4. DispatcherType.ERROR:錯誤跳轉資源
    5. DispatcherType.ASYNC:異步訪問該資源

    注意dispatcherTypes的值可以是一個數組,也就是可以配置多個值,這種情況下,無論是直接訪問,還是請求轉發都會執行過濾器,配置了兩個值

    @WebFilter(value = "/index.jsp", dispatcherTypes = {DispatcherType.FORWARD, DispatcherType.REQUEST}) // 直接訪問或者轉發
    

5. 過濾器鏈(配置多個過濾器)

可以配置多個過濾器,而且可以都生效,但是過濾器有執行順序的問題,上一個的doFilter方法激活下一個的doFilter方法,最后一個doFilter方法激活訪問Servlet的service方法,過濾器鏈的中間任意一個filter沒有調用doFilter方法,那么最終的Servlet的service方法都不會被執行:如果有兩個過濾器分別是Filter1和Filter2,那么執行順序如下:

  1. Filter1執行
  2. Filter2執行
  3. 被請求的資源執行
  4. Filter2執行
  5. Filter1執行

還有就是過濾器的先后問題:

  1. 注解配置方式:按照類名的字符串比較順序,值小的先執行
  2. web.xml配置方式:< filter-mapping >標簽先定義者先執行

四、過濾器的應用(登錄驗證、敏感詞過濾)

1. 登錄驗證

  1. 登錄驗證需求:

    1. 訪問資源,驗證是否已經登錄
    2. 如果登錄了就直接放行
    3. 否則就直接跳轉發到登錄頁面,提示“請您先登錄”
  2. 登錄驗證的過濾器的核心邏輯:

    1. 判斷請求的資源是否登錄相關的資源(登錄頁面相關的資源),如果是,就直接放行;否則就要判斷是否已經登錄

    2. 判斷是否已經登錄,因為如果登錄了都會在session中存儲user鍵,判斷session中是否有user即可判斷是否已經登錄;

      1. 如果已經登錄,直接放行
      2. 否則跳轉到登錄頁面,提示“請您先登錄”
    3. 具體代碼實現:

          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. 敏感詞匯過濾

  1. 敏感詞匯過濾需求:

    1. 對錄入數據進行敏感詞匯過濾
    2. 敏感詞匯參考《敏感詞匯.txt》
    3. 將敏感詞替換為***
  2. 分析:

    1. 請求參數是封裝在request對象的
    2. Filter過濾器doFilter的參數req對象事實上和封裝請求參數的request對象時同一個
    3. 在doFilter中將請求參數中的敏感詞匯替換為***,重新封裝request對象
    4. 放行,chain.doFilter的參數傳入重新封裝的request對象
  3. 增強對象的功能,采用設計模式中的裝飾模式、代理模式可增強對象的功能

    1. 裝飾模式

    2. 代理模式:代理對象代理真實對象,達到增強真實對象功能的模式

      • 真實對象:被代理的對象
      • 代理對象:

      實現方式:

      • 靜態代理:有一個類文件描述代理模式
      • 動態代理:在內存中形成代理
    3. 動態代理的實現步驟

      1. 代理對象和真實對象實現相同的接口(代理對象和真實對象時兄弟);
      2. 代理對象 = Proxy.newProxyInstance();
      3. 使用代理對象調動方法;
      4. 增強方法。
    4. 增強方法的方式

      1. 增強參數列表
      2. 增強返回值類型
      3. 增強方法體執行邏輯
      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();
          }
      }
      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM