JavaWeb Servlet 過濾器(Filter)完整教程

JavaWeb Servlet 過濾器(Filter)完整教程

一、過濾器核心概念

1.1 什么是過濾器

過濾器(Filter)是Jakarta EE 規(guī)范定義的標準組件,它位于客戶端請求和服務器目標資源之間,能夠對請求和響應進行預處理和后處理。過濾器是 Java Web 開發(fā)中最實用的技術之一,完美體現了責任鏈設計模式。

工作機制

  1. 客戶端發(fā)送請求到服務器

  2. 容器創(chuàng)建 HttpServletRequestHttpServletResponse 對象

  3. 請求先經過所有匹配的過濾器的 doFilter 方法

  4. 過濾器決定是否放行請求到目標資源(Servlet/JSP/靜態(tài)資源)

  5. 目標資源處理完成后,響應再次經過過濾器鏈返回給客戶端

生活類比

  • 機場安檢:所有乘客登機前必須經過安檢,安檢人員檢查行李和證件,符合要求才能放行

  • 快遞驛站:快遞到達后,驛站先簽收登記,然后通知收件人取件;寄件時驛站先檢查包裹,再發(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/servletA

  • http://localhost:8080/your-webapp/servletB

  • http://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 四種匹配模式

  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

  2. 路徑匹配(通配符匹配)

    • 寫法:以 / 開頭,以 /* 結尾

    • 示例:

      • <url-pattern>/user/*</url-pattern>:匹配 /user 下的所有子路徑

      • <url-pattern>/*</url-pattern>:匹配所有請求(最常用)

    • 匹配:/user/login、/user/info/123/user/a/b/c

    • 不匹配:/admin/login

  3. 擴展名匹配

    • 寫法:以 *. 開頭,后面跟擴展名(前面不能加 ****/

    • 示例:<url-pattern>*.do</url-pattern>、<url-pattern>*.action</url-pattern>

    • 匹配:/login.do、/user/info.do

    • 不匹配:/login.html、/user/info

  4. 默認匹配(最低優(yōu)先級)

    • 寫法:僅寫一個 /

    • 示例:<url-pattern>/</url-pattern>

    • 作用:當請求沒有匹配到任何其他 Servlet 時觸發(fā),通常用于處理靜態(tài)資源或 404 頁面

    • 注意:與 /* 不同,/ 不會攔截 JSP 頁面(JSP 有專門的內置 Servlet 處理)

3.2 匹配優(yōu)先級規(guī)則

當多個 <url-pattern> 同時匹配一個 URL 時,按以下優(yōu)先級選擇:

  1. 精確匹配 > 路徑匹配 > 擴展名匹配 > 默認匹配

  2. 路徑匹配中,路徑越長優(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 注意事項

  1. 過濾器順序:編碼過濾器必須放在所有過濾器的最前面,否則其他過濾器在讀取參數后再設置編碼將無效

  2. JSP 頁面:JSP 頁面頂部仍需添加 <%@ page contentType="text/html;charset=UTF-8" language="java" %>

  3. GET 請求:Tomcat 8+ 已默認處理 GET 請求編碼,Tomcat 7 及以下版本需要修改 server.xml 配置

八、實戰(zhàn)案例:登錄權限驗證過濾器

在實際項目中,我們通常需要保護某些資源,只有登錄用戶才能訪問。使用過濾器可以統一實現登錄驗證邏輯,避免在每個 Servlet 中重復編寫代碼。

8.1 實現思路

  1. 定義登錄頁面和登錄處理 Servlet

  2. 用戶登錄成功后,將用戶信息存入 Session

  3. 創(chuàng)建登錄驗證過濾器,攔截所有受保護資源

  4. 過濾器檢查 Session 中是否有登錄用戶信息

  5. 如果已登錄,放行請求;如果未登錄,重定向到登錄頁面

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 測試效果

  1. 直接訪問 http://localhost:8080/your-webapp/index,會自動重定向到登錄頁面

  2. 輸入正確的用戶名(admin)和密碼(123456),登錄成功后跳轉到首頁

  3. 此時再訪問首頁,可以正??吹綒g迎信息

  4. 點擊"退出登錄",會銷毀 Session 并返回登錄頁面

九、過濾器與 Servlet 對比

對比項 過濾器(Filter) Servlet
作用 對請求和響應進行預處理和后處理 處理具體的業(yè)務邏輯
執(zhí)行時機 在目標資源之前執(zhí)行 作為目標資源執(zhí)行
能否攔截多個資源 可以,通過 url-pattern 配置 通常一個 Servlet 處理一個或一類請求
能否中斷請求 可以,不調用 chain.doFilter() 即可 不能中斷請求鏈
能否處理響應 可以,在 chain.doFilter() 后處理 只能生成響應
生命周期 Web 應用啟動時創(chuàng)建 默認第一次請求時創(chuàng)建

十、最佳實踐

  1. 單一職責原則:每個過濾器只負責一個功能(如編碼過濾器、登錄過濾器、日志過濾器)

  2. 合理使用過濾器鏈:按照邏輯順序排列過濾器,編碼過濾器放在最前面

  3. 避免在過濾器中執(zhí)行耗時操作:過濾器會影響所有匹配請求的性能

  4. 注意線程安全:過濾器是單例的,不要在過濾器中定義可變的成員變量

  5. 合理設置攔截范圍:不要盲目使用 /* 攔截所有請求,只攔截需要的資源

  6. 使用白名單機制:對于登錄驗證等過濾器,使用白名單排除不需要攔截的路徑

  7. 及時釋放資源:在 destroy() 方法中釋放過濾器占用的資源

( )

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容