師傅說:人生無坦途,累是必須的揹負,看多了,人情人暖,走遍了離合聚散,有時會
在心裡對自己說,我想,我是真的累了,小時候有讀不完的書,長大後有賺不盡的力。
白天在外要奮鬥打拼,把心事都藏起來,笑臉相迎,做一個合格的員工,晚上回家要照顧家人。
把家務都打理的井井有條,做一個稱職的伴侶,習慣了所有事情,自己扛,習慣了所有委屈自己消化,
有時候莫名的低落,什麼話都不想說,只想一個靜靜的發呆,有時會突然的煩躁,什麼事都不想做,
只想讓自己好好的放鬆,偶爾也會嚮往過一份屬於自己的生活。
沒有那麼多責任,要揹負只做自己想做的事,累了就停下類休息吧,煩了就給自己放個假吧。
這個世上沒有鐵打的身體,該休息時就得休息。
這個世上沒有堅強的心靈,該哭泣時就該哭泣。
看看碧海藍天,聽聽輕歌曼舞,會會知己老友,品品清茶,美酒,生活本就可以多姿多彩。
人生說到底,活的是心氣,為累過,方知閒,為苦過,方知甜,隨緣自在,勿忘心安,便是活著的最美狀態。
——————《一禪心靈廟語1》
@
在一個比較複雜的Web應用程式中,通常都有很多URL對映,對應的,也會有多個Servlet來處理URL。
我們考察這樣一個論壇應用程式:
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
/ ┌──────────────┐
│ ┌─────────────>│ IndexServlet │ │
│ └──────────────┘
│ │/signin ┌──────────────┐ │
├─────────────>│SignInServlet │
│ │ └──────────────┘ │
│/signout ┌──────────────┐
┌───────┐ │ ├─────────────>│SignOutServlet│ │
│Browser├─────┤ └──────────────┘
└───────┘ │ │/user/profile ┌──────────────┐ │
├─────────────>│ProfileServlet│
│ │ └──────────────┘ │
│/user/post ┌──────────────┐
│ ├─────────────>│ PostServlet │ │
│ └──────────────┘
│ │/user/reply ┌──────────────┐ │
└─────────────>│ ReplyServlet │
│ └──────────────┘ │
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
各個Servlet設計功能如下:
其中,ProfileServlet、PostServlet和ReplyServlet都需要使用者登入後才能操作,否則,應當直接跳轉到登入頁面。
我們可以直接把判斷登入的邏輯寫到這3個Servlet中,但是,同樣的邏輯重複3次沒有必要,並且,如果後續繼續加Servlet並且也需要驗證登入時,還需要繼續重複這個檢查邏輯。
為了把一些公用邏輯從各個Servlet中抽離出來,JavaEE的Servlet規範還提供了一種Filter元件,即過濾器,它的作用是,在HTTP請求到達Servlet之前,可以被一個或多個Filter預處理,類似列印紀錄檔、登入檢查等邏輯,完全可以放到Filter中。
什麼是 Filter 過濾器:
攔截請求常見的應用場景有:
1.許可權檢查 2.日記操作 3.事務管理 ……等等
一般情況下,都是在過濾器當中編寫公共程式碼。提高程式碼的複用性.
default
是介面中的一個預設方法,基於 jdk8 新特性,預設方法可以不用重寫,如果有需要也是可以重寫的.public default void init(FilterConfig filterConfig) throws ServletException {}
<filter-mapping>
標籤中<url-pattern>
的filter,並按照宣告<filter-mapping>
的順序依次呼叫這些filter的doFilter()方法。doFilter()方法有多個引數,其中引數request
和response
為Web伺服器或filter鏈中的上一個filter傳遞過來的請求和響應物件。引數chain
代表當前filter鏈的物件,只有當前filter物件中的doFilter()方法內部需要呼叫FilterChain
物件的doFilter方法時,才能把請求交付給filter鏈中的下一個filter或目標程式處理。這個是抽象方法,必須重寫。 public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
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() 方法 銷燬 執行了");
}
}
<?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 執行順序該文章的後面會詳細說明,所以請大家,耐心閱讀。謝謝。
當用戶向伺服器傳送request請求時,伺服器接受該請求,並將請求傳送到第一個過濾器中進行處理。如果有多個過濾器,則會依次經過filter2,filter3,…,filter n。接著呼叫Servlet中的service()方法,呼叫完畢後,按照與進入時相反的順序,從過濾器filter n開始,依次經過各個過濾器,直到過濾器filter 1.最終將處理後的結果返回給伺服器,伺服器再反饋給使用者。
filter進行攔截的方式也很簡單,在
HttpServletRequest
到達Servlet之前,filter攔截客戶的HttpServletRequest
,根據需要檢查HttpServletRequest,也可以修改HttpServletRequest頭和資料。在HttpServletRequest到達使用者端之前,攔截HttpServletRequest,根據需要檢查HttpServletRequest,也可以修改HttpServletResponse頭和資料。
想要讓 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 的區別:
關於Filter的設定路徑:不同的 Filter 對映路徑,所攔截使用者請求的也是不一樣的。
關於 Filter 過濾器路徑的設定:大致可以分別如下四種:
精確匹配路徑:/a.do、/b.do、/dept/save。這些設定方式都是精確匹配。
目錄匹配:/admin/*
/*
*.do
字尾匹配。不要以 / 開始,字首:/dept/*
字首匹配。只有完美匹配,一個符號,一個字元,不能錯誤的路徑。Filter 只會過濾使用者存取該:/target/dep
路徑的資訊才會攔截判斷是否放行。其他使用者存取的路徑一概不會進行 Filter 過濾器過濾
<filter-mapping>
<filter-name>Filter</filter-name>
<url-pattern>/target/dep</url-pattern> <!--精確匹配-->
</filter-mapping>
Filter 會過濾使用者存取該:/admin/*
admin子路徑包含 admin路徑的資訊:比如:/admin/test/admin/test/test2才會攔截判斷是否放行。其他使用者存取的路徑一概不會進行 Filter 過濾器過濾。
<filter-mapping>
<filter-name>Filter</filter-name>
<url-pattern>/admin/*</url-pattern> <!--目錄匹配-->
</filter-mapping>
字尾路徑匹配:以上設定的路徑,表示請求地址必須以.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>
/*
表示對應任何請求地址,Filter 過濾器都會進行一個攔截判斷是否放行,那麼這個路徑的資源不存在也會,進行一個攔截判斷是否放行。這種方式雖然簡單,但是,這個代價比較到,效率低,對於特殊的路徑請求要放行的你可能需要編寫大量的邏輯判斷進行一個攔截放行。不建議使用。
<filter-mapping>
<filter-name>Filter</filter-name>
<url-pattern>/*</url-pattern> <!--目錄匹配-->
</filter-mapping>
一個 Servelt 是可以設定多個 Filter 過濾器的,當我們設定了多個 Filter 過濾器,其中 Filter 過濾器的執行順序該如何設定呢?
從上面文章的內容,我們知道了 Filter 的對映路徑設定有兩種方式:
@WebFilter()
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);
}
}
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 ");
}
}
關於 oa 專案的,大家可以移步至