JavaWeb 中 Filter過濾器

Sea發表於2023-05-09

Filter過濾器

在這裡插入圖片描述

每博一文案

師傅說:人生無坦途,累是必須的揹負,看多了,人情人暖,走遍了離合聚散,有時會
在心裡對自己說,我想,我是真的累了,小時候有讀不完的書,長大後有賺不盡的力。
白天在外要奮鬥打拼,把心事都藏起來,笑臉相迎,做一個合格的員工,晚上回家要照顧家人。
把家務都打理的井井有條,做一個稱職的伴侶,習慣了所有事情,自己扛,習慣了所有委屈自己消化,
有時候莫名的低落,什麼話都不想說,只想一個靜靜的發呆,有時會突然的煩躁,什麼事都不想做,
只想讓自己好好的放鬆,偶爾也會嚮往過一份屬於自己的生活。
沒有那麼多責任,要揹負只做自己想做的事,累了就停下類休息吧,煩了就給自己放個假吧。
這個世上沒有鐵打的身體,該休息時就得休息。
這個世上沒有堅強的心靈,該哭泣時就該哭泣。
看看碧海藍天,聽聽輕歌曼舞,會會知己老友,品品清茶,美酒,生活本就可以多姿多彩。
人生說到底,活的是心氣,為累過,方知閒,為苦過,方知甜,隨緣自在,勿忘心安,便是活著的最美狀態。
                                       ——————《一禪心靈廟語1》

@

1. Filter 過濾器的概述

在這裡插入圖片描述

在一個比較複雜的Web應用程式中,通常都有很多URL對映,對應的,也會有多個Servlet來處理URL。

我們考察這樣一個論壇應用程式:

            ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
               /             ┌──────────────┐
            │ ┌─────────────>│ IndexServlet │ │
              │              └──────────────┘
            │ │/signin       ┌──────────────┐ │
              ├─────────────>│SignInServlet │
            │ │              └──────────────┘ │
              │/signout      ┌──────────────┐
┌───────┐   │ ├─────────────>│SignOutServlet│ │
│Browser├─────┤              └──────────────┘
└───────┘   │ │/user/profile ┌──────────────┐ │
              ├─────────────>│ProfileServlet│
            │ │              └──────────────┘ │
              │/user/post    ┌──────────────┐
            │ ├─────────────>│ PostServlet  │ │
              │              └──────────────┘
            │ │/user/reply   ┌──────────────┐ │
              └─────────────>│ ReplyServlet │
            │                └──────────────┘ │
             ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─

各個Servlet設計功能如下:

  • IndexServlet:瀏覽帖子;
  • SignInServlet:登入;
  • SignOutServlet:登出;
  • ProfileServlet:修改使用者資料;
  • PostServlet:發帖;
  • ReplyServlet:回覆。

其中,ProfileServlet、PostServlet和ReplyServlet都需要使用者登入後才能操作,否則,應當直接跳轉到登入頁面。

我們可以直接把判斷登入的邏輯寫到這3個Servlet中,但是,同樣的邏輯重複3次沒有必要,並且,如果後續繼續加Servlet並且也需要驗證登入時,還需要繼續重複這個檢查邏輯。

為了把一些公用邏輯從各個Servlet中抽離出來,JavaEE的Servlet規範還提供了一種Filter元件,即過濾器,它的作用是,在HTTP請求到達Servlet之前,可以被一個或多個Filter預處理,類似列印日誌、登入檢查等邏輯,完全可以放到Filter中。

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

什麼是 Filter 過濾器:

  1. Filter 過濾器它是 JavaWeb 的三大元件之一。
    三大元件分別是:Servlet 程式、Listener 監聽器、Filter 過濾器
  2. Filter 過濾器它是 JavaEE 的規範。也就是介面
  3. Filter 過濾器它的作用是:攔截請求,過濾響應。

攔截請求常見的應用場景有:
1.許可權檢查 2.日記操作 3.事務管理 ……等等

一般情況下,都是在過濾器當中編寫公共程式碼。提高程式碼的複用性.

2. Filter 過濾器的編寫

  • 第一步:編寫一個Java類實現一個介面:jarkata.servlet.Filter。並且實現這個介面當中所有的方法。

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

  • init( )方法:在Filter物件第一次被建立之後呼叫,並且只呼叫一次。與Servlet中的init()方法類似,filter中的init()方法用於初始化過濾器。開發者可以在init()方法中完成與構造方法類似的初始化功能。如果初始化程式碼中要用到FilterConfig物件,則這些初始化程式碼只能在filter的init()方法中編寫,而不能在構造方法中編寫。 default 是介面中的一個預設方法,基於 jdk8 新特性,預設方法可以不用重寫,如果有需要也是可以重寫的.
public default void init(FilterConfig filterConfig) throws ServletException {}
  • doFilter( )方法:只要使用者傳送一次請求,則執行一次。傳送N次請求,則執行N次。在這個方法中編寫過濾規則。需要注意的是,伺服器響應的時候,該方法也是會被執行的。doFilter方法類似於Servlet介面的service()方法。當客戶端請求目標資源時,容器會篩選出符合<filter-mapping>標籤中<url-pattern>的filter,並按照宣告<filter-mapping>的順序依次呼叫這些filter的doFilter()方法。doFilter()方法有多個引數,其中引數requestresponse為Web伺服器或filter鏈中的上一個filter傳遞過來的請求和響應物件。引數chain代表當前filter鏈的物件,只有當前filter物件中的doFilter()方法內部需要呼叫FilterChain物件的doFilter方法時,才能把請求交付給filter鏈中的下一個filter或目標程式處理。這個是抽象方法,必須重寫。
 public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
  • destroy( )方法:在Filter物件被釋放/銷燬之前呼叫,並且只呼叫一次。 filter中的destroy()方法Servlet中的destroy()作用類似,在Web伺服器解除安裝filter物件之前被呼叫,用於釋放被filter物件開啟的資源。 default 是介面中的一個預設方法,基於 jdk8 新特性,預設方法可以不用重寫,如果有需要也是可以重寫的.
 public default void destroy() {}
package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;

import java.io.IOException;

public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("TestFilter 中的 init() 方法 初始化 執行了");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("TestFilter 中的 doFilter() 方法執行了");
    }

    @Override
    public void destroy() {
        System.out.println("TestFilter 中的 destroy() 方法 銷燬 執行了");
    }
}

  • 第二步:在web.xml檔案中對 Filter進行配置。這個配置和 Servlet很像。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">

    <filter>
        <!--        兩個 name 名是要保持一致的-->
        <filter-name>filter</filter-name>
        <!--        對應的全類路徑名,全類限定名-->
        <filter-class>com.RainbowSea.filter.TestFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filter</filter-name>
        <!--        Filter 的對映路徑,以 / 開始-->
        <url-pattern>/a.do</url-pattern>
    </filter-mapping>
    
    
</web-app>

如下是關於: Filter的所有的 web.xml 中的配置屬性資訊:

標籤 作用
<filter> 指定一個過濾器
<filter-name> 用於為過濾器指定一個名稱,該元素的內容不能為空
<filter-class> 用於指定過濾器的完整的限定類名
<init-param> 用於為過濾器指定初始化引數
<param-value> <init-param>的子引數,用於指定引數的名稱
<filter-mapping> 用於設定一個filter所負責攔截的資源
<filter-name> <filter-mapping>子元素,用於設定filter的註冊名稱。該值必須是在<filter>元素中宣告過的過濾器的名稱
<url-pattern> 用於設定filter所攔截的請求路徑
<servlet-name> 用於指定過濾器所攔截的Servlet名稱

執行效果:

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

如果Servlet版本大於3.0,也可以使用註解 @WebFilter()的方式來配置filter。

]

如下:

package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;


@WebFilter("/a.do")
public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("TestFilter 中的 init() 方法 初始化 執行了");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("TestFilter 中的 doFilter() 方法執行了");
    }

    @Override
    public void destroy() {
        System.out.println("TestFilter 中的 destroy() 方法 銷燬 執行了");
    }
}

如下是關於:@WebFilter() 的屬性的說明:

屬性 型別 是否必須 說明
asyncSupported boolean 指定filter是否支援非同步模式
dispatcherTypes DispatcherType[] 指定filter對哪種方式的請求進行過濾
filterName String filter名稱
initParams WebInitParam[] 配置引數
displayName String filter顯示名
servletNames String[] 指定對哪些Servlet進行過濾
urlPatterns/value String[] 兩個屬性作用相同,指定攔截的路徑

web.xml可以配置的filter屬性都可以透過@WebServlet的方式進行配置。不過一般不推薦使用註解方式來配置filter,因為如果存在多個過濾器,使用web.xml配置filter可以控制過濾器的執行順序,如果使用了註解方式,則不可以這樣做了。該 Filter 執行順序該文章的後面會詳細說明,所以請大家,耐心閱讀。謝謝。

3. Filter 過濾器的執行過程解析

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

當使用者向伺服器傳送request請求時,伺服器接受該請求,並將請求傳送到第一個過濾器中進行處理。如果有多個過濾器,則會依次經過filter2,filter3,…,filter n。接著呼叫Servlet中的service()方法,呼叫完畢後,按照與進入時相反的順序,從過濾器filter n開始,依次經過各個過濾器,直到過濾器filter 1.最終將處理後的結果返回給伺服器,伺服器再反饋給使用者。

filter進行攔截的方式也很簡單,在HttpServletRequest到達Servlet之前,filter攔截客戶的HttpServletRequest,根據需要檢查HttpServletRequest,也可以修改HttpServletRequest頭和資料。在HttpServletRequest到達客戶端之前,攔截HttpServletRequest,根據需要檢查HttpServletRequest,也可以修改HttpServletResponse頭和資料。

3.1 Filter 過濾結合 Servlet 的使用

想要讓 Filter 可以過濾使用者對 Servlet 傳送的請求,必須滿足如下兩個條件:

  • 第一個:在 Filter 過濾器當中的 doFilter() 方法中編寫:chain.doFilter(request, response) 方法:該方法的作用是:執行下一個過濾器,如果下面沒有過濾器了(Filter 過濾器之間的 對映路徑是相同的情況下),執行最終的Servlet(在Servlet 與 Filter 過濾器的對映路徑是相同的情況下。)
@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 執行下一個過濾器,如果下面沒有過濾器了,執行最終的Servlet
        chain.doFilter(request,response);
    } 
  • 第二:使用者傳送的請求路徑是否和Servlet的請求路徑一致。而 Filter過濾器的對映路徑是否包含/和Servlet的請求路徑一致。只有 Filter 過濾器對映路徑包含/和 Servlet 的請求對映路徑是一致的,Filter才會過濾該使用者方法的請求資訊。
  • 注意:Filter的優先順序,天生的就比Servlet優先順序高。:比如:/a.do 對應一個Filter,也對應一個Servlet。那麼一定是先執行Filter,然後再執行Servlet。

舉例:

在這裡插入圖片描述

package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;


@WebFilter("/a.do")
public class TestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("TestFilter 中的 doFilter() 方法 begin 開始執行了");

        // 表示:執行下一個 Filter(同一個 對映的路徑,如果有下一個Filter 的話),沒有就執行(同一個對映的路徑的 Servlet )
        chain.doFilter(request,response);

        System.out.println("TestFilter 中的 doFilter() 方法 end 執行結束");
    }

}

package com.RainbowSea.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;


@WebServlet("/a.do")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
        System.out.println("AServlet 執行了");
    }
}

測試效果:

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

案例舉例:

LoginFilter 過濾器,獲取到客戶端傳送過來的請求,進行一個過濾,判斷使用者的賬號和密碼是否正確,正確的話,LoginFilter 過濾器放行,到 LogServlet 表示登入成功。如果使用者的賬號和密碼是錯誤的,則讓提示使用者密碼錯誤,請重新登入。

這裡為了方便演示核心,我們就將 賬號和密碼寫死了,賬號為: admin ,密碼為 123

package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;


@WebFilter("/login")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest requ, ServletResponse resp, FilterChain chain) throws IOException,
            ServletException {


        HttpServletRequest request = (HttpServletRequest) requ;
        HttpServletResponse  response= (HttpServletResponse) resp;

        request.setCharacterEncoding("UTF-8");  // 設定獲取到的請求資訊的字元編碼:

        // 獲取到使用者的請求資訊

        String name = request.getParameter("user");
        String password = request.getParameter("password");


        // 過濾器:判斷使用者登入的賬號和密碼是否正確
        if ("admin".equals(name) && "123".equals(password)) {
            // 正確:放行
            // 表示:執行下一個 Filter(同一個 對映的路徑,如果有下一個Filter 的話),沒有就執行(同一個對映的路徑的 Servlet )
            chain.doFilter(request,response);

        } else {
            // 跳轉至登入失敗的頁面
            response.sendRedirect(request.getContextPath()+"/error.jsp");
        }
    }
}

package com.RainbowSea.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;


@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charSet=utf-8");

        PrintWriter writer = response.getWriter();

        writer.println("登入成功");
    }
}


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<h3>登入失敗,請重新登入</h3>

</body>
</html>

在這裡插入圖片描述

在這裡插入圖片描述

Filter 過濾器的生命週期

在這裡插入圖片描述

當Web容器啟動時,會根據web.xml中宣告的filter順序依次例項化這些filter。然後在Web應用程式載入時呼叫init()方法,隨機客戶端有請求時呼叫doFilter()方法,並且根據實際情況的不同,doFilter()方法可能被呼叫多次。最後Web應用程式解除安裝時呼叫destroy()方法。

Filter 過濾器與 Servlet 的區別:

  • servlet物件預設情況下,在伺服器啟動的時候是不會新建物件的。
  • Filter物件預設情況下,在伺服器啟動的時候會新建物件。
  • Servlet是單例的。Filter也是單例的。(單例項,但是它們都是假單例,因為真單例的構造器是 private 的,而這兩個類都不是私有的構造器)
  • Filter 和Servlet物件生命週期一致。
  • 唯一的區別:Filter預設情況下,在伺服器啟動階段就例項化。Servlet不會。
  • Filter的優先順序,天生的就比Servlet優先順序高。比如:/a.do 對應一個Filter,也對應一個Servlet。那麼一定是先執行Filter,然後再執行Servlet。

4. Filter 過濾器的攔截路徑:

關於Filter的配置路徑:不同的 Filter 對映路徑,所攔截使用者請求的也是不一樣的。

關於 Filter 過濾器路徑的配置:大致可以分別如下四種:

  • 精確匹配路徑:/a.do、/b.do、/dept/save。這些配置方式都是精確匹配。

  • 目錄匹配:/admin/*

  • 匹配所有路徑: /*
  • 前字尾名路徑匹配:字尾:*.do 字尾匹配。不要以 / 開始,字首:/dept/* 字首匹配。

4.1 精確匹配路徑

只有完美匹配,一個符號,一個字元,不能錯誤的路徑。Filter 只會過濾使用者訪問該:/target/dep路徑的資訊才會攔截判斷是否放行。其他使用者訪問的路徑一概不會進行 Filter 過濾器過濾

<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>/target/dep</url-pattern>  <!--精確匹配-->
</filter-mapping>

4.2 目錄匹配

Filter 會過濾使用者訪問該:/admin/* admin子路徑包含 admin路徑的資訊:比如:/admin/test/admin/test/test2才會攔截判斷是否放行。其他使用者訪問的路徑一概不會進行 Filter 過濾器過濾。

<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>/admin/*</url-pattern> <!--目錄匹配-->
</filter-mapping>

4.3 前字尾名路徑匹配

字尾路徑匹配:以上配置的路徑,表示請求地址必須以.do 結尾才會被 Filter 過濾器攔截判斷是否放行。

注意的是:不要以 / 開始 ,不然就失效了。

<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>*.do</url-pattern> <!--目錄匹配-->
</filter-mapping>

字首路徑匹配:以上配置的路徑,表示請求地址必須以/admin/ 開頭才會被 Filter 過濾器攔截判斷是否放行

<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>/admin/*</url-pattern> <!--目錄匹配-->
</filter-mapping>

4.4 所有路徑匹配

/* 表示對應任何請求地址,Filter 過濾器都會進行一個攔截判斷是否放行,那麼這個路徑的資源不存在也會,進行一個攔截判斷是否放行。這種方式雖然簡單,但是,這個代價比較到,效率低,對於特殊的路徑請求要放行的你可能需要編寫大量的邏輯判斷進行一個攔截放行。不建議使用。

<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>/*</url-pattern> <!--目錄匹配-->
</filter-mapping>

5. 設定 Filter 執行順序

一個 Servelt 是可以設定多個 Filter 過濾器的,當我們設定了多個 Filter 過濾器,其中 Filter 過濾器的執行順序該如何設定呢?

從上面文章的內容,我們知道了 Filter 的對映路徑設定有兩種方式:

  1. 註解:@WebFilter()
  2. 配置 web.xml 檔案的方式。這種方式 推薦使用

兩種設定 Filter的方式不同,對於設定的執行順序的方式也是不一樣的。

對於 Filter 執行順序的設定,雖然有兩種方式,但是推薦使用 web.xml 配置檔案的方式,因為使用 註解@WebFilter() 的方式的話,我們需要更加定義的類名的字母順序的方式來,設定Filter的執行順序,這樣會改變一個類名的見名之意。而如果是使用 web.xml 配置檔案的方式,直接更加 Filtet 編寫的配置是先後順序就可以了。

過濾器的呼叫順序,遵循棧資料結構。

第一種:註解的方式:設定Filter過濾器的執行順序:

註解的方式:

執行順序是:比較 Filter 這個類名。就是你定義的這個類名 implements (實現) Filter 的類名

在這裡插入圖片描述

  • 比如:FilterA和FilterB,則先執行FilterA
  • 比如:Filter1和Filter2,則先執行Filter1

舉例驗證:

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

透過:配置 web.xml 檔案的方式,如何設定 Filter 的執行順序:

web.xml 中是:依靠 <filter-mapping>標籤的配置位置,越靠上優先順序越高,就越先執行其中的 Filter的 doFilter() 方法。

舉例證實:

在這裡插入圖片描述

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">

    <filter>
        <filter-name>FilterB</filter-name>
        <filter-class>com.RainbowSea.filter.FilterB</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>FilterB</filter-name>
        <url-pattern>/A</url-pattern>
    </filter-mapping>

    <filter>
        <!--        兩個 name 名是要保持一致的-->
        <filter-name>FilterA</filter-name>
        <!--        對應的全類路徑名,全類限定名-->
        <filter-class>com.RainbowSea.filter.FilterA</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>FilterA</filter-name>
        <!--        Filter 的對映路徑,以 / 開始-->
        <url-pattern>/A</url-pattern>
    </filter-mapping>




</web-app>
package com.RainbowSea.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;


@WebServlet("/A")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException,
            IOException {
        System.out.println("AServlet doGet()  執行了");


    }
}

package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;


import java.io.IOException;



public class FilterB implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        System.out.println("FilterB doFilter() 執行了");

        // 表示執行後面的(對映路徑相同的Filter 過濾器),如果後面沒有的話執行(對映路徑相同的 Servlet)
        chain.doFilter(request,response);

    }
}

package com.RainbowSea.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;


import java.io.IOException;




public class FilterA implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("FilterA  doFilter() 執行了");

        // 表示執行後面的(對映路徑相同的Filter 過濾器),如果後面沒有的話執行(對映路徑相同的 Servlet)
        chain.doFilter(request,response);
    }
}

在這裡插入圖片描述

6. Filter 過濾器中的責任鏈設計模式思想

23種設計模式 : 責任鏈設計模式

過濾器最大的優點:

在程式編譯階段不會確定呼叫順序。因為Filter的呼叫順序是配置到web.xml檔案中的,只要修改web.xml配置檔案中filter-mapping的順序就可以調整Filter的執行順序。顯然Filter的執行順序是在程式執行階段動態組合的。那麼這種設計模式被稱為責任鏈設計模式。

責任鏈設計模式最大的核心思想:在程式執行階段,動態的組合程式的呼叫順序。在上面對於 Filter的使用當中,我們已經體驗到了,Filter 的動態呼叫其中的 doFilter() 方法,透過修改其中的 web.xml 對 Filter 的配置順序。

下面我們演示一下不是 :責任鏈設計模式的方式:

如下這種方式:是我們寫死了的,想要改變其中的執行順序,就必須透過修改其中的原始碼當中的,程式碼的執行順序,無法透過透過配置檔案的方式,修改呼叫的順序。

package com.RainbowSea.filter;

public class Test {
    public static void main(String[] args) {
        m1();
    }

    private static void m1() {
        System.out.println("m1() begin ");
        m2();
        System.out.println("m1() end ");
    }

    private static void m2() {
        System.out.println("m2() begin ");
        m3();
        System.out.println("m2() end ");
    }

    private static void m3() {
        System.out.println("m3() begin ");

        System.out.println("m3() end ");
    }
}

在這裡插入圖片描述

7. 運用 Filter 過濾器的方式最佳化 oa 專案的一個登入驗證:

關於 oa 專案的,大家可以移步至??? B/S 結構系統的 快取機制(Cookie) 以及基於 cookie 機制實現 oa 十天免登入的功能_ChinaRainbowSea的部落格-CSDN部落格 先了解,有助於後面的閱讀。

思路:

有什麼情況下不能攔截: 要過濾透過:

目前編寫的路徑是/* 表示所有請求均攔截:

使用者訪問 index.jsp 的時候不能攔截, 要放行:
使用者登入過了,這個時候,需要放行。
使用者要去登入,這個也是不能攔截,要放行:讓使用者登入。
其他情況:比如,沒有登入過,就想訪問資源的話,過濾攔截,
登入失敗,過濾攔截
使用者退出,不能攔截,要讓其過去

需要注意的是:這裡我們 Filter 過濾器當中的 doFilter() 方法中的 request 是來自: ServletRequest 介面的,當時如下使用的一些方法 sendRedirect() 是來自於: HttpServletRequest 這個介面的,所以我們需要強制型別轉換一下。 HttpServletRequest 是 extends 繼承了 ServletRequest 介面的。

在這裡插入圖片描述

在這裡插入圖片描述

package com.RainbowSea.Filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;

@WebFilter("/*")
public class LoginCheckFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        // 需要強制型別轉換以下,因為 下面的一些getServletPath 方法是來自 HttpServletRequest的
        //不是 來自 ServletRequest 的
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String servletPath = request.getServletPath();  // 獲取到瀏覽器當中的uri

        // 獲取session 這個 session 是不不需要新建的
        // 只是獲取當前session ,獲取不到這返回null,
        HttpSession session = request.getSession(false);  // 獲取到伺服器當中的session ,沒有不會建立的



        // 過濾器:
        /*
        有什麼情況下不能攔截: 要過濾透過:
          1. 目前編寫的路徑是/* 表示所有請求均攔截:

          使用者訪問 index.jsp 的時候不能攔截, 要放行:
          使用者登入過了,這個時候,需要放行。
          使用者要去登入,這個也是不能攔截,要放行:讓使用者登入。
          其他情況:比如,沒有登入過,就想訪問資源的話,過濾攔截,
          登入失敗,過濾攔截
          使用者退出,不能攔截,要讓其過去


           if ("/dept/list".equals(servletPath)) {
                doList(request, response);
            } else if ("/dept/detail".equals(servletPath)) {
                doDetail(request, response);
            } else if ("/dept/delete".equals(servletPath)) {
                doElete(request,response);
            } else if("/dept/save".equals(servletPath)) {
                doSave(request,response);
            } else if("/dept/modify".equals(servletPath)) {
                doModify(request,response);


         */

        if("/index.jsp".equals(servletPath) || (("/welcome").equals(servletPath)) ||
                ( session != null && session.getAttribute("username") != null)
        || "/user/login".equals(servletPath) || "/user/exit".equals(servletPath)) {
            // 雙重的判斷,一個是 session 會話域要存在,其次是 會話域當中儲存了名為 "username" 的資訊

            chain.doFilter(request,response);  // 放行,讓其向下一個過濾器,或者是Servlet 執行
        } else {
            response.sendRedirect(request.getContextPath() + "/index.jsp");  // 訪問的web 站點的根即可,自動找到的是名為 index.jsp
            // 的歡迎頁面(注意這裡被最佳化修改了:區域性優先)注意:這裡修改了,需要指明index.jsp登入頁面了,因為區域性優先
        }
    }
}

8. 總結:

  1. Filter 過濾器它是 JavaEE 的規範。也就是介面Filter 過濾器它的作用是:攔截請求,過濾響應。
  2. Filter 過濾器的常用的三個方法:
  • init方法:在Filter物件第一次被建立之後呼叫,並且只呼叫一次。
  • doFilter方法:只要使用者傳送一次請求,則執行一次。傳送N次請求,則執行N次。在這個方法中編寫過濾規則。
  • destroy方法:在Filter物件被釋放/銷燬之前呼叫,並且只呼叫一次。
  1. 目標Servlet是否執行,取決於兩個條件:
  • 第一:在過濾器當中是否編寫了:chain.doFilter(request, response); 程式碼。
  • 第二:使用者傳送的請求路徑是否和Servlet的請求路徑一致。
  1. chain.doFilter(request, response); 這行程式碼的作用:執行下一個過濾器,如果下面沒有過濾器了,執行最終的Servlet。

  2. 注意:Filter的優先順序,天生的就比Servlet優先順序高。

  • /a.do 對應一個Filter,也對應一個Servlet。那麼一定是先執行Filter,然後再執行Servlet。
  1. Filter 的執行原理圖:
    在這裡插入圖片描述

  2. Filter 過濾器攔截路徑:

    • /a.do、/b.do、/dept/save。這些配置方式都是精確匹配。
    • /* 匹配所有路徑。
    • *.do 字尾匹配。不要以 / 開始
    • /dept/* 字首匹配
  3. Filter 過濾器的設定執行順序:兩種方式:

  • 在web.xml檔案中進行配置的時候,依靠filter-mapping標籤的配置位置,越靠上優先順序越高。

  • @WebFilter的時候,執行順序是:比較Filter這個類名。

    • 比如:FilterA和FilterB,則先執行FilterA。
    • 比如:Filter1和Filter2,則先執行Filter1
  1. Filter 過濾器中的責任鏈設計模式思想:責任鏈設計模式最大的核心思想:在程式執行階段,動態的組合程式的呼叫順序
  2. 編寫 Filter 過濾器的技巧:先想明白一個就是:哪些資源/功能方法是需要驗證才能放行的,哪些是不需要驗證直接就放行透過的。想明白這兩點,基本上編寫的 Filter 過濾器沒有任何問題了。

9. 最後:

⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ 感謝如下博主的分享: ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

限於自身水平,其中存在的錯誤,希望大家給予指教,韓信點兵——多多益善,謝謝大家,江湖再見,後會有期!!!

在這裡插入圖片描述

相關文章