Filter 介紹
過濾器的基本概念
Servlet 過濾器從字面可理解為經過一層層的過濾處理才達到使用的要求,而其實 Servlet 過濾器就是服務器與客戶端請求與響應的中間層組件。
在實際項目開發中 Servlet 過濾器主要用於對瀏覽器的請求進行過濾處理,將過濾后的請求再轉給下一個資源。
過濾器是以一種組件的形式綁定到 Web 應用程序當中的,與其他的 Web 應用程序組件不同的是,過濾器是采用了“鏈”的方式進行處理的,如下所示:
Filter
Filter(過濾器)是 JavaWeb 三大組件之一(另外兩個是 Servlet 和 Listener),是在 2000 年發布的 Servlet2.3 規范中加入的一個接口,是 Servlet 規范中非常實用的技術。
當需要限制用戶訪問某些資源或者在處理請求時提前處理某些資源的時候,就可以使用過濾器(Filter)完成。
- 當一個請求訪問服務器資源時,服務器首先判斷會是否有過濾器與請求資源相關聯,如果有,過濾器會先將請求攔截下來,完成一些特定的功能,再由過濾器決定是否繼續交給請求資源進行處理。
- 響應也是類似的。
Filter 應用場景:
- URL 級別的權限控制
- 過濾敏感詞匯
- 中文亂碼問題
- ...
Servlet 的 Filter 特點:
-
聲明式的
通過在 web.xml 配置文件中聲明,允許添加、刪除過濾器,而無須改動任何應用程序代碼或 JSP 頁面。 -
靈活的
過濾器可用於客戶端的直接調用執行預處理和后期的處理工作,通過過濾鏈可以實現一些靈活的功能。 -
可移植的
由於現今各個 Web 容器都是以 Servlet 的規范進行設計的,因此 Servlet 過濾器同樣是跨容器的。 -
可重用的
基於其可移植性和聲明式的配置方式,Filter 是可重用的。
總的來說,Servlet 的過濾器是通過一個配置文件來靈活的聲明的模塊化可重用組件。過濾器動態的截獲傳入的請求和傳出的響應,在不修改程序代碼的情況下,透明的添加或刪除他們。其獨立於任何平台和 Web 容器。
Filter API
Filter
Filter 是一個接口。如果想實現過濾器的功能,則必須實現該接口。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | init(FilterConfig config) | 初始化方法 |
void | doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | 對請求資源和響應資源進行攔截 |
void | destroy() | 銷毀方法 |
配置方式:
-
方式一:使用注解
@WebFilter("攔截路徑")
-
方式二:web.xml 配置
FilterChain
-
FilterChain 是一個接口,代表過濾器鏈對象,由 Servlet 容器提供實現類對象,我們直接使用即可。
-
過濾器可以定義多個,就會組成過濾器鏈。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | doFilter(ServletRequest request, ServletResponse response) | 放行方法 |
- 如果有多個過濾器,則會在第一個過濾器中再調用下一個過濾器,依次類推,直到到達最終訪問資源。
- 如果只有一個過濾器,放行時,就會直接到達最終訪問資源。
FilterConfig
FilterConfig 是一個接口,代表過濾器的配置對象,可以加載一些初始化參數。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
String | getFilterName() | 獲取過濾器對象名稱 |
String | getInitParameter(String key) | 根據 key 獲取 value |
Enumeration<String> | getInitParameterNames() | 獲取所有參數的 key |
ServletContext | getServletContext() | 獲取應用上下文對象 |
Filter 工作原理
Filter 的體系結構
如其名字所暗示的一樣,Servlet 過濾器用於攔截傳入的請求和傳出的響應,並監視、修改處理 Web 工程中的數據流。過濾器是一個可插入的自由組件。Web 資源可以不配置過濾器、也可以配置單個過濾器,也可以配置多個過濾器,形成一個過濾器鏈。Filter 接受用戶的請求,並決定將請求轉發給鏈中的下一個組件,或者終止請求直接向客戶端返回一個響應。如果請求被轉發了,它將被傳遞給鏈中的下一個過濾器(以 web.xml 過濾器的配置順序為標准)。這個請求在通過過濾鏈並被服務器處理之后,一個響應將以相反的順序通過該鏈發送回去。這樣,請求和響應都得到了處理。
Filter 可以應用在客戶端和 Servlet 之間、Servlet 和 Servlet 或 JSP 之間,並且可以通過配置言息,靈活的使用那個過濾器。
基於上述體系結構的描述,Filter 工作原理如下圖所示:
- 客戶端瀏覽器在訪問 Web 服務器的某個具體資源的時候,經過過濾器 1 中 code l 代碼塊的相關處理之后,將請求傳遞給過濾鏈中的下一個過濾器 2(過濾鏈的順序以配置文件中的順序為基准)
- 過濾器 2 處理完之后,請求就根據傳遞的 Servlet 完成相應的邏輯。
- 返回響應的過程類似,只是過濾鏈的順序相反。
Filter 生命周期
- 出生:當應用加載時,執行初始化方法。
- 活着:只要應用一直提供服務,對象就一直存在。
- 死亡:當應用卸載時(執行銷毀方法)或服務器宕機時,對象消亡。
Filter 的實例對象在內存中也只有一份,所以 Filter 也是單例的。
import javax.servlet.*;
import java.io.IOException;
/*
過濾器生命周期
*/
//@WebFilter("/*")
public class FilterDemo03 implements Filter{
/*
初始化方法
*/
@Override
public void init(FilterConfig filterConfig) {
System.out.println("對象初始化成功了...");
}
/*
提供服務方法
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filterDemo03執行了...");
//處理亂碼
servletResponse.setContentType("text/html;charset=UTF-8");
//放行
filterChain.doFilter(servletRequest,servletResponse);
}
/*
對象銷毀
*/
@Override
public void destroy() {
System.out.println("對象銷毀了...");
}
}
Filter 實現案例
Filter 的創建過程:
要編寫一個過濾器必須實現 Filter 接口,實現其接口規定的方法。
- 實現 javax.Servlet.Filter 接口;
- 實現 init() 方法,讀取過濾器的初始化參數;
- 實現 doFilter()方法,完成對請求或響應的過濾;
- 調用 FilterChain 接口對象的 doFilter() 方法,向后續的過濾器傳遞請求或響應。
1)編寫接收和處理請求的 Servlet:
public class ServletDemo1 extends HttpServlet {
/**
* 處理請求的方法
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ServletDemo1接收到了請求");
req.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
2)配置 Servlet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="true">
<!--配置Servlet-->
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>com.itheima.web.servlet.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/ServletDemo1</url-pattern>
</servlet-mapping>
</web-app>
3)編寫 index.jsp:
<%-- Created by IntelliJ IDEA. --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>主頁面</title>
</head>
<body>
<a href="${pageContext.request.contextPath}/ServletDemo1">訪問ServletDemo1</a>
</body>
</html>
4)編寫 success.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>成功頁面</title>
</head>
<body>
<% System.out.println("success.jsp執行了"); %>
執行成功!
</body>
</html>
5)編寫 Filter:
public class FilterDemo1 implements Filter {
/**
* 過濾器的核心方法
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/**
* 如果不寫此段代碼,控制台會輸出兩次:FilterDemo1攔截到了請求。
*/
HttpServletRequest req = (HttpServletRequest) request;
String requestURI = req.getRequestURI();
if (requestURI.contains("favicon.ico")) {
return;
}
System.out.println("FilterDemo1攔截到了請求");
}
}
6)配置 Filter:
<!--配置過濾器-->
<filter>
<filter-name>FilterDemo1</filter-name>
<filter-class>com.itheima.web.filter.FilterDemo1</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterDemo1</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
運行結果分析:
當我們啟動服務並在地址欄輸入訪問地址后,發現瀏覽器任何內容都沒有,控制台卻輸出了【FilterDemo1攔截到了請求】,也就是說在訪問任何資源的時候,都先經過了過濾器。
這是因為,我們在配置過濾器的攔截規則時,使用了/*
,表示訪問當前應用下的任何資源,此過濾器都會起作用。
除了這種全部過濾的規則之外,它還支持特定類型的過濾配置。我們可以稍作調整,修改的方式如下:
新的問題是,我們攔截下來了,但點擊鏈接發送請求,運行結果是:
對此,需要對過濾器執行放行操作,才能讓它繼續執行,那么如何放行的?
我們需要使用FilterChain
中的doFilter
方法放行。
繼續修改:在FilterDemo1
的doFilter
方法后添加一行代碼
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/**
* 如果不寫此段代碼,控制台會輸出兩次:FilterDemo1攔截到了請求。
HttpServletRequest req = (HttpServletRequest) request;
String requestURI = req.getRequestURI();
if (requestURI.contains("favicon.ico")) {
return;
}*/
System.out.println("FilterDemo1攔截到了請求");
// 過濾器放行
chain.doFilter(request,response);
// 新增一行代碼
System.out.println("FilterDemo1放行之后,又回到了doFilter方法");
}
運行結果如下,我們發現過濾器放行之后執行完目標資源,最后仍會回到過濾器中。
FilterConfig:過濾器配置對象
1)新增過濾器 FilterDemo2 :
public class FilterDemo2 implements Filter {
private FilterConfig filterConfig;
/**
* 初始化方法
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("FilterDemo2的初始化方法執行了");
// 給過濾器配置對象賦值
this.filterConfig = filterConfig;
}
/**
* 過濾器的核心方法
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("FilterDemo2攔截到了請求");
// 根據名稱獲取過濾器的初始化參數
String paramValue = filterConfig.getInitParameter("filterInitParamName");
System.out.println(paramValue);
// 獲取過濾器初始化參數名稱的枚舉
Enumeration<String> initNames = filterConfig.getInitParameterNames();
while(initNames.hasMoreElements()){
String initName = initNames.nextElement();
String initValue = filterConfig.getInitParameter(initName);
System.out.println(initName+","+initValue);
}
// 獲取ServletContext對象
ServletContext servletContext = filterConfig.getServletContext();
System.out.println(servletContext);
// 獲取過濾器名稱
String filterName = filterConfig.getFilterName();
System.out.println(filterName);
// 過濾器放行
chain.doFilter(request, response);
}
/**
* 銷毀方法
*/
@Override
public void destroy() {
System.out.println("FilterDemo2的銷毀方法執行了");
}
}
2)配置 FilterDemo2 :
<filter>
<filter-name>FilterDemo2</filter-name>
<filter-class>com.itheima.web.filter.FilterDemo2</filter-class>
<!--配置過濾器的初始化參數-->
<init-param>
<param-name>filterInitParamName</param-name>
<param-value>filterInitParamValue</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>FilterDemo2</filter-name>
<url-pattern>/ServletDemo1</url-pattern>
</filter-mapping>
運行效果:
Filter 五種攔截行為
<filter>
<filter-name>filterDemo05</filter-name>
<filter-class>com.itheima.filter.FilterDemo05</filter-class>
<!-- 配置開啟異步支持,當dispatcher配置ASYNC時,需要配置此行 -->
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>filterDemo05</filter-name>
<!-- <url-pattern>/error.jsp</url-pattern> -->
<url-pattern>/index.jsp</url-pattern>
<!-- 過濾請求(默認值)-->
<dispatcher>REQUEST</dispatcher>
<!-- 過濾全局錯誤頁面:當由服務器調用全局錯誤頁面時,過濾器工作 -->
<dispatcher>ERROR</dispatcher>
<!-- 過濾請求轉發:當請求轉發時,過濾器工作 -->
<dispatcher>FORWARD</dispatcher>
<!-- 過濾請求包含:當請求包含時,過濾器工作。它只能過濾動態包含,jsp的include指令是靜態包含,過濾器不會起作用 -->
<dispatcher>INCLUDE</dispatcher>
<!-- 過濾異步類型,它要求我們在filter標簽中配置開啟異步支持 -->
<dispatcher>ASYNC</dispatcher>
</filter-mapping>
<!-- 配置全局錯誤頁面 -->
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/error.jsp</location>
</error-page>
</web-app>