JavaWeb Servlet 過濾器(Filter)完整教程
一、過濾器核心概念
1.1 什么是過濾器
過濾器(Filter)是Jakarta EE 規(guī)范定義的標準組件,它位于客戶端請求和服務器目標資源之間,能夠對請求和響應進行預處理和后處理。過濾器是 Java Web 開發(fā)中最實用的技術之一,完美體現了責任鏈設計模式。
工作機制:
客戶端發(fā)送請求到服務器
容器創(chuàng)建
HttpServletRequest和HttpServletResponse對象請求先經過所有匹配的過濾器的
doFilter方法過濾器決定是否放行請求到目標資源(Servlet/JSP/靜態(tài)資源)
目標資源處理完成后,響應再次經過過濾器鏈返回給客戶端
生活類比:
機場安檢:所有乘客登機前必須經過安檢,安檢人員檢查行李和證件,符合要求才能放行
快遞驛站:快遞到達后,驛站先簽收登記,然后通知收件人取件;寄件時驛站先檢查包裹,再發(fā)往目的地
1.2 過濾器的典型應用場景
統一編碼處理:解決全站中文亂碼問題
登錄權限驗證:攔截未登錄用戶的受保護資源請求
敏感字符過濾:過濾用戶輸入中的非法或敏感內容
日志記錄與審計:記錄所有請求的訪問信息和耗時
性能監(jiān)控:統計接口響應時間,分析系統性能瓶頸
跨域資源共享(CORS):統一處理跨域請求
事務控制:為請求開啟和提交事務,實現聲明式事務
1.3 Filter 接口 API
所有自定義過濾器都必須實現 jakarta.servlet.Filter 接口,該接口定義了三個核心方法:
| 方法簽名 | 執(zhí)行時機 | 作用 |
|---|---|---|
default void init(FilterConfig filterConfig) |
Web 應用啟動時,過濾器對象創(chuàng)建后立即執(zhí)行 | 初始化過濾器,讀取配置參數 |
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
每次匹配的請求到達時執(zhí)行 | 核心過濾邏輯,決定是否放行 |
default void destroy() |
Web 應用關閉時,過濾器對象銷毀前執(zhí)行 | 釋放過濾器占用的資源 |
接口源碼(Jakarta Servlet 5.0):
package jakarta.servlet;
import java.io.IOException;
public interface Filter {
default public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
default public void destroy() {}
}
二、過濾器快速入門:日志記錄過濾器
我們通過一個請求日志記錄過濾器來演示過濾器的完整開發(fā)流程,該過濾器將記錄所有請求的訪問時間、路徑和處理耗時。
2.1 步驟 1:創(chuàng)建過濾器類
package com.example.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 日志記錄過濾器:記錄所有請求的訪問信息和處理耗時
*/
public class LoggingFilter implements Filter {
private SimpleDateFormat dateFormat;
// 初始化方法:Web應用啟動時執(zhí)行一次
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化日期格式化器
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("日志過濾器初始化完成");
}
// 核心過濾方法:每次請求都會執(zhí)行
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
// 1. 類型轉換:將父接口轉換為HTTP專用子接口
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 2. 請求預處理:記錄請求開始時間和路徑
String requestURI = request.getRequestURI();
String startTime = dateFormat.format(new Date());
long startMillis = System.currentTimeMillis();
System.out.printf("[請求開始] %s | 路徑:%s%n", startTime, requestURI);
try {
// 3. 放行請求:將請求傳遞給下一個過濾器或目標資源
chain.doFilter(request, response);
} finally {
// 4. 響應后處理:記錄請求耗時
long endMillis = System.currentTimeMillis();
long duration = endMillis - startMillis;
System.out.printf("[請求結束] %s | 路徑:%s | 耗時:%dms%n", startTime, requestURI, duration);
}
}
// 銷毀方法:Web應用關閉時執(zhí)行一次
@Override
public void destroy() {
System.out.println("日志過濾器已銷毀");
}
}
2.2 步驟 2:創(chuàng)建測試用 Servlet
為了驗證過濾器效果,我們創(chuàng)建兩個簡單的 Servlet 作為目標資源:
ServletA.java
package com.example.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("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 模擬業(yè)務處理耗時
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
resp.getWriter().write("ServletA 處理完成");
}
}
ServletB.java
package com.example.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("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 模擬業(yè)務處理耗時
try {
Thread.sleep(15);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
resp.getWriter().write("ServletB 處理完成");
}
}
2.3 步驟 3:配置過濾器
過濾器有兩種配置方式:web.xml 配置和注解配置,我們先介紹傳統的 web.xml 方式。
在 WEB-INF/web.xml 文件中添加以下配置:
<?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">
<!-- 1. 定義過濾器 -->
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.example.filter.LoggingFilter</filter-class>
</filter>
<!-- 2. 配置過濾器的攔截規(guī)則 -->
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<!-- 攔截 /servletA 的請求 -->
<url-pattern>/servletA</url-pattern>
<!-- 攔截所有 .html 靜態(tài)資源 -->
<url-pattern>*.html</url-pattern>
<!-- 攔截名為 servletB 的 Servlet(通過 Servlet 名稱) -->
<servlet-name>com.example.servlet.ServletB</servlet-name>
</filter-mapping>
</web-app>
2.4 測試效果
啟動 Tomcat 服務器,分別訪問以下地址:
http://localhost:8080/your-webapp/servletAhttp://localhost:8080/your-webapp/servletBhttp://localhost:8080/your-webapp/index.html(如果存在)
控制臺輸出示例:
日志過濾器初始化完成
[請求開始] 2024-05-20 14:30:00 | 路徑:/your-webapp/servletA
[請求結束] 2024-05-20 14:30:00 | 路徑:/your-webapp/servletA | 耗時:12ms
[請求開始] 2024-05-20 14:30:05 | 路徑:/your-webapp/servletB
[請求結束] 2024-05-20 14:30:05 | 路徑:/your-webapp/servletB | 耗時:17ms
三、<url-pattern> 匹配規(guī)則詳解
過濾器的攔截范圍由 <url-pattern> 標簽控制,Servlet 規(guī)范定義了 4 種匹配模式,優(yōu)先級從高到低排列。
3.1 四種匹配模式
-
精確匹配(最高優(yōu)先級)
寫法:以
/開頭,寫死完整路徑示例:
<url-pattern>/user/login</url-pattern>匹配:
http://localhost:8080/app/user/login不匹配:
http://localhost:8080/app/user/login/、http://localhost:8080/app/user/info
-
路徑匹配(通配符匹配)
寫法:以
/開頭,以/*結尾-
示例:
<url-pattern>/user/*</url-pattern>:匹配/user下的所有子路徑<url-pattern>/*</url-pattern>:匹配所有請求(最常用)
匹配:
/user/login、/user/info/123、/user/a/b/c不匹配:
/admin/login
-
擴展名匹配
寫法:以
*.開頭,后面跟擴展名(前面不能加 ****/)示例:
<url-pattern>*.do</url-pattern>、<url-pattern>*.action</url-pattern>匹配:
/login.do、/user/info.do不匹配:
/login.html、/user/info
-
默認匹配(最低優(yōu)先級)
寫法:僅寫一個
/示例:
<url-pattern>/</url-pattern>作用:當請求沒有匹配到任何其他 Servlet 時觸發(fā),通常用于處理靜態(tài)資源或 404 頁面
注意:與
/*不同,/不會攔截 JSP 頁面(JSP 有專門的內置 Servlet 處理)
3.2 匹配優(yōu)先級規(guī)則
當多個 <url-pattern> 同時匹配一個 URL 時,按以下優(yōu)先級選擇:
精確匹配 > 路徑匹配 > 擴展名匹配 > 默認匹配
路徑匹配中,路徑越長優(yōu)先級越高(如
/user/admin/*優(yōu)先于/user/*)
3.3 常見錯誤寫法
? 錯誤:/user/*.do(路徑匹配和擴展名匹配不能混合使用)
? 錯誤:user/*(必須以 / 開頭)
? 錯誤:*.do/(擴展名匹配后面不能加 /)
? 錯誤:/*.*(不支持這種通配符寫法)
四、過濾器生命周期
過濾器的生命周期由 Web 容器管理,與 Servlet 類似但略有不同:
| 生命周期階段 | 對應方法 | 執(zhí)行時機 | 執(zhí)行次數 |
|---|---|---|---|
| 對象創(chuàng)建 | 構造方法 | Web 應用啟動時 | 1 次 |
| 初始化 | init(FilterConfig) |
構造方法執(zhí)行后立即執(zhí)行 | 1 次 |
| 請求處理 | doFilter() |
每次匹配的請求到達時 | 多次 |
| 對象銷毀 | destroy() |
Web 應用關閉時 | 1 次 |
關鍵特點:
過濾器是單例的,整個應用中只有一個實例
所有請求共享同一個過濾器實例,因此要注意線程安全問題
過濾器在 Web 應用啟動時就會被創(chuàng)建和初始化,而不是第一次請求時
過濾器的銷毀發(fā)生在 Web 應用正常關閉時
生命周期測試代碼:
package com.example.filter;
import jakarta.servlet.*;
import java.io.IOException;
public class LifecycleTestFilter implements Filter {
// 構造方法
public LifecycleTestFilter() {
System.out.println("1. 過濾器對象創(chuàng)建:構造方法執(zhí)行");
}
// 初始化方法
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("2. 過濾器初始化:init 方法執(zhí)行");
}
// 過濾方法
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("3. 處理請求:doFilter 方法執(zhí)行");
chain.doFilter(request, response);
}
// 銷毀方法
@Override
public void destroy() {
System.out.println("4. 過濾器銷毀:destroy 方法執(zhí)行");
}
}
五、過濾器鏈(FilterChain)
當多個過濾器同時匹配同一個請求時,它們會按照配置順序形成一個過濾器鏈。
5.1 過濾器鏈執(zhí)行順序
請求按照
filter-mapping的從上到下順序依次經過各個過濾器響應按照相反順序依次經過各個過濾器
任何一個過濾器沒有調用
chain.doFilter()方法,請求都會被中斷,不再繼續(xù)傳遞
5.2 過濾器鏈示例
我們創(chuàng)建三個過濾器來演示執(zhí)行順序:
Filter1.java
package com.example.filter;
import jakarta.servlet.*;
import java.io.IOException;
public class Filter1 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Filter1:請求預處理");
chain.doFilter(request, response);
System.out.println("Filter1:響應后處理");
}
}
Filter2.java
package com.example.filter;
import jakarta.servlet.*;
import java.io.IOException;
public class Filter2 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Filter2:請求預處理");
chain.doFilter(request, response);
System.out.println("Filter2:響應后處理");
}
}
Filter3.java
package com.example.filter;
import jakarta.servlet.*;
import java.io.IOException;
public class Filter3 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Filter3:請求預處理");
chain.doFilter(request, response);
System.out.println("Filter3:響應后處理");
}
}
web.xml 配置:
<filter>
<filter-name>filter1</filter-name>
<filter-class>com.example.filter.Filter1</filter-class>
</filter>
<filter>
<filter-name>filter2</filter-name>
<filter-class>com.example.filter.Filter2</filter-class>
</filter>
<filter>
<filter-name>filter3</filter-name>
<filter-class>com.example.filter.Filter3</filter-class>
</filter>
<!-- filter-mapping 的順序決定了過濾器的執(zhí)行順序 -->
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filter2</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filter3</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
創(chuàng)建測試 Servlet:
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("TestServlet:處理請求");
resp.getWriter().write("測試完成");
}
}
控制臺輸出:
Filter1:請求預處理
Filter2:請求預處理
Filter3:請求預處理
TestServlet:處理請求
Filter3:響應后處理
Filter2:響應后處理
Filter1:響應后處理
5.3 過濾器鏈執(zhí)行順序圖解
客戶端請求
↓
Filter1.doFilter() 前
↓
Filter2.doFilter() 前
↓
Filter3.doFilter() 前
↓
目標資源(Servlet)處理請求
↓
Filter3.doFilter() 后
↓
Filter2.doFilter() 后
↓
Filter1.doFilter() 后
↓
響應返回客戶端
六、注解方式配置過濾器
除了 web.xml 配置外,還可以使用 @WebFilter 注解來配置過濾器,這種方式更簡潔,適合現代 Web 開發(fā)。
6.1 @WebFilter 注解常用屬性
| 屬性 | 作用 | 對應 web.xml 標簽 |
|---|---|---|
filterName |
過濾器名稱 | <filter-name> |
urlPatterns / value
|
攔截的 URL 模式 | <url-pattern> |
servletNames |
攔截的 Servlet 名稱 | <servlet-name> |
initParams |
初始化參數 | <init-param> |
dispatcherTypes |
攔截的請求分發(fā)類型 | <dispatcher> |
asyncSupported |
是否支持異步處理 | <async-supported> |
6.2 注解配置示例
將之前的日志過濾器改造成注解配置方式:
package com.example.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 日志記錄過濾器(注解配置方式)
*/
@WebFilter(
filterName = "loggingFilter",
urlPatterns = {"/servletA", "*.html"},
servletNames = {"com.example.servlet.ServletB"},
initParams = {
@WebInitParam(name = "datePattern", value = "yyyy-MM-dd HH:mm:ss")
}
)
public class LoggingFilter implements Filter {
private SimpleDateFormat dateFormat;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 從注解中獲取初始化參數
String datePattern = filterConfig.getInitParameter("datePattern");
dateFormat = new SimpleDateFormat(datePattern);
System.out.println("日志過濾器初始化完成(注解方式)");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
String startTime = dateFormat.format(new Date());
long startMillis = System.currentTimeMillis();
System.out.printf("[請求開始] %s | 路徑:%s%n", startTime, requestURI);
try {
chain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startMillis;
System.out.printf("[請求結束] %s | 路徑:%s | 耗時:%dms%n", startTime, requestURI, duration);
}
}
@Override
public void destroy() {
System.out.println("日志過濾器已銷毀");
}
}
6.3 注解配置注意事項
使用
@WebFilter注解時,需要確保 Web 應用的web.xml文件中沒有配置<metadata-complete="true">(該配置會關閉注解掃描)注解配置的過濾器執(zhí)行順序無法直接控制,如果需要嚴格控制順序,建議使用 web.xml 配置
注解配置和 web.xml 配置可以混合使用
七、實戰(zhàn)案例:統一中文編碼處理過濾器
中文亂碼是 Java Web 開發(fā)中最常見的問題之一,使用過濾器可以一次性解決全站的中文亂碼問題。
7.1 問題分析
POST 請求的中文亂碼:需要在讀取參數前設置
request.setCharacterEncoding("UTF-8")響應的中文亂碼:需要設置
response.setContentType("text/html;charset=UTF-8")Tomcat 8+ 已經默認將 GET 請求的編碼設置為 UTF-8,無需額外處理
7.2 代碼實現
package com.example.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 統一編碼處理過濾器:解決全站中文亂碼問題
*/
@WebFilter("/*") // 攔截所有請求
public class CharacterEncodingFilter implements Filter {
private String encoding = "UTF-8"; // 默認編碼
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 支持通過初始化參數自定義編碼
String encodingParam = filterConfig.getInitParameter("encoding");
if (encodingParam != null && !encodingParam.isEmpty()) {
encoding = encodingParam;
}
System.out.println("編碼過濾器初始化完成,使用編碼:" + encoding);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 1. 設置請求編碼(解決 POST 請求中文亂碼)
request.setCharacterEncoding(encoding);
// 2. 設置響應編碼(解決響應中文亂碼)
response.setContentType("text/html;charset=" + encoding);
response.setCharacterEncoding(encoding);
// 3. 放行請求
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 無需釋放資源
}
}
7.3 注意事項
過濾器順序:編碼過濾器必須放在所有過濾器的最前面,否則其他過濾器在讀取參數后再設置編碼將無效
JSP 頁面:JSP 頁面頂部仍需添加
<%@ page contentType="text/html;charset=UTF-8" language="java" %>GET 請求:Tomcat 8+ 已默認處理 GET 請求編碼,Tomcat 7 及以下版本需要修改
server.xml配置
八、實戰(zhàn)案例:登錄權限驗證過濾器
在實際項目中,我們通常需要保護某些資源,只有登錄用戶才能訪問。使用過濾器可以統一實現登錄驗證邏輯,避免在每個 Servlet 中重復編寫代碼。
8.1 實現思路
定義登錄頁面和登錄處理 Servlet
用戶登錄成功后,將用戶信息存入 Session
創(chuàng)建登錄驗證過濾器,攔截所有受保護資源
過濾器檢查 Session 中是否有登錄用戶信息
如果已登錄,放行請求;如果未登錄,重定向到登錄頁面
8.2 代碼實現
登錄頁面 login.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用戶登錄</title>
</head>
<body>
<h3>用戶登錄</h3>
<form action="login" method="post">
用戶名:<input type="text" name="username" required><br>
密碼:<input type="password" name="password" required><br>
<input type="submit" value="登錄">
</form>
</body>
</html>
登錄處理 Servlet LoginServlet.java:
package com.example.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 jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
// 簡單驗證(實際項目中應查詢數據庫)
if ("admin".equals(username) && "123456".equals(password)) {
// 登錄成功,將用戶信息存入 Session
HttpSession session = req.getSession();
session.setAttribute("loginUser", username);
// 重定向到首頁
resp.sendRedirect("index");
} else {
// 登錄失敗
resp.getWriter().write("用戶名或密碼錯誤!<a href='login.html'>返回登錄</a>");
}
}
}
首頁 Servlet IndexServlet.java:
package com.example.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("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = (String) req.getSession().getAttribute("loginUser");
resp.getWriter().write("<h3>歡迎您," + username + "!</h3>");
resp.getWriter().write("<a href='logout'>退出登錄</a>");
}
}
退出登錄 Servlet LogoutServlet.java:
package com.example.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("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 銷毀 Session
req.getSession().invalidate();
// 重定向到登錄頁面
resp.sendRedirect("login.html");
}
}
登錄驗證過濾器 LoginFilter.java:
package com.example.filter;
import jakarta.servlet.*;
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 LoginFilter implements Filter {
// 不需要登錄就能訪問的路徑(白名單)
private static final String[] WHITE_LIST = {
"/login.html",
"/login",
"/css/",
"/js/",
"/images/"
};
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
// 1. 檢查是否是白名單路徑
for (String path : WHITE_LIST) {
if (requestURI.contains(path)) {
// 白名單路徑,直接放行
chain.doFilter(request, response);
return;
}
}
// 2. 檢查用戶是否已登錄
HttpSession session = request.getSession(false); // false 表示如果沒有 Session 則返回 null
if (session != null && session.getAttribute("loginUser") != null) {
// 已登錄,放行
chain.doFilter(request, response);
} else {
// 未登錄,重定向到登錄頁面
response.sendRedirect(request.getContextPath() + "/login.html");
}
}
}
8.3 測試效果
直接訪問
http://localhost:8080/your-webapp/index,會自動重定向到登錄頁面輸入正確的用戶名(admin)和密碼(123456),登錄成功后跳轉到首頁
此時再訪問首頁,可以正??吹綒g迎信息
點擊"退出登錄",會銷毀 Session 并返回登錄頁面
九、過濾器與 Servlet 對比
| 對比項 | 過濾器(Filter) | Servlet |
|---|---|---|
| 作用 | 對請求和響應進行預處理和后處理 | 處理具體的業(yè)務邏輯 |
| 執(zhí)行時機 | 在目標資源之前執(zhí)行 | 作為目標資源執(zhí)行 |
| 能否攔截多個資源 | 可以,通過 url-pattern 配置 | 通常一個 Servlet 處理一個或一類請求 |
| 能否中斷請求 | 可以,不調用 chain.doFilter() 即可 | 不能中斷請求鏈 |
| 能否處理響應 | 可以,在 chain.doFilter() 后處理 | 只能生成響應 |
| 生命周期 | Web 應用啟動時創(chuàng)建 | 默認第一次請求時創(chuàng)建 |
十、最佳實踐
單一職責原則:每個過濾器只負責一個功能(如編碼過濾器、登錄過濾器、日志過濾器)
合理使用過濾器鏈:按照邏輯順序排列過濾器,編碼過濾器放在最前面
避免在過濾器中執(zhí)行耗時操作:過濾器會影響所有匹配請求的性能
注意線程安全:過濾器是單例的,不要在過濾器中定義可變的成員變量
合理設置攔截范圍:不要盲目使用
/*攔截所有請求,只攔截需要的資源使用白名單機制:對于登錄驗證等過濾器,使用白名單排除不需要攔截的路徑
及時釋放資源:在
destroy()方法中釋放過濾器占用的資源
( )