1. 程式人生 > >大資料WEB階段(十五)JavaEE三大核心技術之過濾器

大資料WEB階段(十五)JavaEE三大核心技術之過濾器

Filter過濾器

一、Filter 過濾器概述

  1. Filter是JavaEE三大核心技術(Servlet 、 Filter 、 Listener)之一
  2. FIlter作用是攔截對資源的訪問 , 攔截下來後可以控制是否允許通過 , 或者在允許通過前後做一些額外的操作 。
  3. 所謂的攔截其實就是對代表請求的request、物件和代表 響應的response物件攔截下來 , 進行控制
  4. 一個過濾器可能攔截多個資源 , 一個資源也可能被多個過濾器攔截
  5. 這種多個攔截器攔截一個資源的模式成為責任鏈模式 。
  6. 常用場景:
    1. 就與URL的訪問許可權控制
    2. 全站亂碼解決過濾器
    3. 過濾敏感詞彙
    4. 壓縮響應

二、 過濾器的開發

  1. 想要開發一個過濾器 , 需要兩個步驟
    1. 寫一個類實現Filter介面
    2. 在web.xml中配置過濾器
  2. Filter介面:
    1. init(FilterConfig)
      1. 初始化的方法 , 當Filter被初始化時 , 呼叫此方法 , 執行初始化操作
    2. destory()
      1. 銷燬方法, 在Filter被銷燬之前呼叫 , 執行善後操作
    3. doFilter(ServletRequest request , ServletResponse response , FilterChain chain)
      1. 核心方法 ,在存活期間 , 過濾器攔截到對資源的訪問 會造成此方法的執行 , 需要在這個方法中設計過濾器的核心邏輯程式碼
  3. 配置過濾器

    <filter> -- 配置一個過濾器
        <filter-name>FirstFilter</filter-name> -- 過濾器的名字
        <filter-class>com.tarena.filter.FirstFilter</filter-class> -- 過濾器的類
    </filter>
    <filter-mapping> -- 過濾器的攔截路徑配置,可以配置多個
        <filter-name>FirstFilter</filter-name> -- 為哪個名字的過濾器配置
        <url-pattern>/*</url-pattern> -- 攔截哪個路徑資源可以配置多個
        <servlet-name>XxxServlet</servlet-name> -- 攔截哪個名字的Servlet
        <dispatcher></dispatcher> -- 指定過濾器攔截哪種方式對資源的訪問,可以取值為REQUEST FORWARD INCLUDE ERROR,如果不配置,預設只攔截REQUEST方式的訪問。可以配置多個。
    </filter-mapping>
    

三、生命週期

  1. 在web應用啟動時 , 會建立處web應用中配置的過濾器物件 , 創建出過濾器物件會立即呼叫init方法進行初始化操作 , 之後一直存活 , 直到web應用被銷燬時 , Filter跟著被銷燬.在銷燬之前會自動呼叫destory方法執行善後操作 。 在存活期間 , 每當攔截到資源訪問 , 就執行doFilter方法 , 來執行過濾器的 邏輯 , 如果不做操作 , 則 預設攔截 , 可以通過FilterChain類的物件的 doFilter方法實現對資源訪問的放行 。 並且可以在doFilter前後做一些操作 。

四、 細節

  1. 如果一個資源被多個過濾器攔截 , 多個 攔截器的攔截順序取決於在web.xml檔案中配置過濾器時的先後順序 。
  2. 多個 過濾器的執行 , 類似於方法 一層一層呼叫的過程 ,, 一層一層往裡鑽, 然後在一層層一層往外出 。

五、 和Filter開發相關的物件

  1. FilterConfig:

    1. init方法的引數
    2. 代表FIlter在web.xml檔案中的配置物件
    3. 可以用來獲取Filter在Web.xml檔案中的初始化配置引數
    4. 可以用來獲取ServletContext物件

      public void init(FilterConfig filterConfig) throws ServletException {
          System.out.println("init....");
      
          //1.filterConfig功能1:獲取filter的初始化引數
          Enumeration<String> names = filterConfig.getInitParameterNames();
          while(names.hasMoreElements()){
              String name = names.nextElement();
              String value = filterConfig.getInitParameter(name);
              System.out.println(name+"~"+value);
          }
      
      
          //2.獲取ServletContext物件
          ServletContext sc = filterConfig.getServletContext();
      
      }
      
  2. FilterChain:
    1. doFilter方法引數
    2. 代表過濾器鏈
    3. 提供了doFilter方法 ,放行當前過濾器 , 執行後續過濾器 , 如果後續沒有過濾器則呼叫到相應的資源 。

六、Filter案例

  1. 全站亂碼解決過濾器

    1. 在web開發過程中 , 存在請求引數亂碼和響應輸出亂碼 。
    2. 之前的開發中 , 在所有的Servlet和jsp頁面中 , 需要手動解決這兩種亂碼
    3. 可以通過開發過濾器攔截所有的資源訪問 , 在過濾器中解決全站亂碼問題 。
    4. 具體解決請求響應亂碼問題:

      1. 在web.xml檔案 中配置全域性的編碼型別
      <!-- 全域性配置 -->
      <context-param>
          <param-name>encode</param-name>
          <param-value>utf-8</param-value>
      </context-param>
      2. 在過濾器初始化時獲取全域性配置的編碼 , 並儲存到過濾器中
      private String encode = null;
      public void init(FilterConfig config) throws ServletException {
          encode = (String) config.getServletContext().getAttribute("encode");
      }
      
      3. 解決響應亂碼:
      在doFilter方法中
      response.setCharacterEncoding(encode);
      response.setContentType("text/html;charset="+encode);
      4. 解決請求引數亂碼
          //方案一:
      //      request.setCharacterEncoding(encode); //只能解決Post請求引數的亂碼
          //可以解決Post和Get請求型別的引數亂碼
          //但是在轉碼的時候需要指定具體的引數名稱  ,轉碼之後要重新放入request中供servlet拿取更是不能實現 , 所以 不可行
      //      String param = new String(request.getParameter("xxxx").getBytes("iso-8859-1") , encode);
          //方案二:
          //request中的請求引數本身無法改變 
          //那麼 換一個思路 想辦法改造和獲取請求引數相關的方法 在方法內加上解決亂碼的程式碼 
          //這樣通過這些方法獲取請求引數時 解決好亂碼再返回 用起來就感覺 亂碼被解決了一樣
              //改造原有request方法方案一: 
              //繼承
              //繼承只能先改造在建立例項 , 但是現在已經有了request物件,  就算通過繼承改造了ServletRequest類也不會影響到已有的物件 , 排除
              //改造原有request方法方案二:
              //裝飾設計模式
              //1. 新建一個類 , 實現與被改造物件相同的介面
              //2. 通過狗仔方法傳入被改造的物件並儲存在本類中
              //3. 然後實現介面中所有的方法 , 如果需要改造則在對應的方法裡寫出邏輯 , 如果不需要改造的方法 ,則直接通過傳入的沒被改造的引數物件呼叫原有的方法即可
              //這種方案 的缺點 ,如果被改造的方法 中方法過多時 ,這個操作會十分繁瑣 。
              //改造原有的request物件方案三:
              //在裝飾設計模式的基礎上實現
              //通過原始碼 發現Servlet包下已經提供了一個ServletRequestWrapper類 , 它實現了與ServletRequest相同的介面
              //也就是說他就是java中已經提供的供開發者修改request物件中方法的入口
              //新建一個類繼承ServletRequestWrapper類之後 ,通過構造方法 把原始物件傳進去 , 然後只重寫需要改造的方法即可
              程式碼//
              public class EncodeFilter implements Filter{
          /**
           * 當前web應用編碼集
           */
          private String encode = null;
      
          /**
           * 初始化方法
           */
          public void init(FilterConfig filterConfig) throws ServletException {
              //獲取ServletContext物件
              ServletContext sc = filterConfig.getServletContext();
              //讀取初始化引數 中的 編碼集 配置
              this.encode = sc.getInitParameter("encode");
          }
      
          /**
           * 過濾方法
           */
          public void doFilter(final ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {
              //--全站響應亂碼
              response.setCharacterEncoding(encode);
              response.setContentType("text/html;charset="+encode);
              //--全站請求亂碼 - 通過裝飾器 裝飾request 修改其中的和獲取請求引數相關的方法 增加了亂碼解決的程式碼
              ServletRequest myReq = new MyServletRequest((HttpServletRequest) request);
              //放行資源
              chain.doFilter(myReq, response);
          }
      
          /**
           * 銷燬方法
           */
          public void destroy() {
      
          }
      
          /**
           * 內部類 ServletRequest的裝飾類 改造了獲取請求引數相關的方法 增加了亂碼解決的程式碼
           */
          //繼承了HttpServletRequestWrapper ,這個父類本身就是 HttpServletRequest的裝飾器  在其中提供方法的預設的實現 不想改造的方法 不用管 想改造的方法 覆蓋父類方法即可
          class MyServletRequest extends HttpServletRequestWrapper{
      
              private ServletRequest request = null;
      
              private boolean hasNotEncode = true;
      
              //構造器 接受傳入的request儲存在類的內部
              public MyServletRequest(HttpServletRequest request) {
                  super(request);
                  this.request = request;
              }
      
              //覆蓋和獲取請求引數相關的方法
              @Override
              public Map<String,String[]> getParameterMap() {
                  try {
                      //1.獲取真正request的請求引數組成的map
                      Map<String,String[]>  map = request.getParameterMap();
                      if(hasNotEncode){//由於真正的request對此map會快取 所以解決亂碼的操作 只需要做一次 此處通過hasNotEncode來控制
                          //2.遍歷map
                          for(Map.Entry<String, String[]>entry : map.entrySet()){
                              //3.獲取當前遍歷到的值的陣列
                              String [] values = entry.getValue();
                              //4.遍歷值的陣列 
                              for(int i = 0;i<values.length;i++){
                                  //5.解決亂碼 存回陣列
                                  values[i] = new String(values[i].getBytes("iso8859-1"),encode);
                              }
                          }
                          hasNotEncode = false;
                      }
                      //6.返回解決完亂碼的map
                      return map;
                  } catch (Exception e) {
                      e.printStackTrace();
                      throw new RuntimeException(e);
                  }
              }
      
  2. 使用者30天內自動登入

    1. 在處理使用者登入時 , 判斷使用者是否勾選30天內自動登入 , 如果使用者名稱密碼正確且勾選過該選項 , 則傳送cookie , 將使用者名稱密碼儲存30天。 為了安全起見 , 儲存之前先對密碼進行MD5加密
    2. 之後使用者在來訪問時經過自動登入過濾器被攔截 , 如果使用者未登入 , 且帶了自動登入的cookie , 並且其中的使用者名稱密碼都正確 , 則給給使用者給自動登入 。但是無論自動登入與否都要對url放行 。
    3. 在LoginServlet中新增使用者自動登入邏輯: 如果使用者勾選 了自動登入 , 則將使用者資訊新增進cookie中儲存在本地
    4. 在AutoLoginFilter中 攔截所有請求 , 先判斷是否登入 , 在判斷是否有自動登入cookie ,最後判斷使用者密碼是否正確
    5. 自動登入過濾器原始碼

      public class AutoLoginFilter implements Filter{
      
          public void destroy() {
      
          }
      
          public void doFilter(ServletRequest request, ServletResponse response,
                  FilterChain chain) throws IOException, ServletException {
              HttpServletRequest req = (HttpServletRequest) request;
      //      1. 判斷該使用者是否 已經登入
              if(req.getSession(false) == null ||req.getSession(false).getAttribute("user") == null){
                  System.out.println("使用者未登入");
      //      2. 判斷訪問時是否帶有自動登入cookie
                  Cookie[] cookies = req.getCookies();
                  for(Cookie c :cookies){
                      if("autologin".equals(c.getName())){
                          System.out.println("帶有自動登入cookie");
                          String v = c.getValue();
                          String[] vs = URLDecoder.decode(v , "utf-8").split("#");
                          UserService us = BaseFactory.getBase().getInstance(UserService.class);
      //      3. 驗證使用者名稱密碼是否正確
                          User user = us.login(vs[0], vs[1]);
                          if(user != null){
                              System.out.println("開始登入");
                              //三個條件都滿足 , 新增登入標記
                              req.getSession().setAttribute("user", user);
                              System.out.println("自動登入成功");
                          }
                          break;
                      }
                  }
              }
      //      4. 無論是否自動 登入成功 , 都放行訪問。  放行訪問
              chain.doFilter(request , response);
          }
      
          public void init(FilterConfig arg0) throws ServletException {
              // TODO Auto-generated method stub
      
          }
      
      }
      

七、MD5加密演算法

  1. 又稱資料摘要演算法 , 資料指紋演算法
  2. 任意長度的二進位制檔案計算出128位二進位制的再要資訊 , 通常轉換為32位16進位制顯示 。
  3. 明文相同算出的密文一定相同
  4. 明文不同算出的密文一定不同 (概率極低 , 所以一般認為是唯一的)
  5. 只能由明文算成密文 ,, 不能有密文算成明文
  6. 應用:
    1. 加密儲存資料
    2. 檔案完整性校驗
    3. 數字簽名