JavaWeb Servlet 过滤器(Filter)完整教程

JavaWeb Servlet 过滤器(Filter)完整教程

一、过滤器核心概念

1.1 什么是过滤器

过滤器(Filter)是Jakarta EE 规范定义的标准组件,它位于客户端请求和服务器目标资源之间,能够对请求和响应进行预处理和后处理。过滤器是 Java Web 开发中最实用的技术之一,完美体现了责任链设计模式

工作机制

  1. 客户端发送请求到服务器

  2. 容器创建 HttpServletRequestHttpServletResponse 对象

  3. 请求先经过所有匹配的过滤器的 doFilter 方法

  4. 过滤器决定是否放行请求到目标资源(Servlet/JSP/静态资源)

  5. 目标资源处理完成后,响应再次经过过滤器链返回给客户端

生活类比

  • 机场安检:所有乘客登机前必须经过安检,安检人员检查行李和证件,符合要求才能放行

  • 快递驿站:快递到达后,驿站先签收登记,然后通知收件人取件;寄件时驿站先检查包裹,再发往目的地

1.2 过滤器的典型应用场景

  • 统一编码处理:解决全站中文乱码问题

  • 登录权限验证:拦截未登录用户的受保护资源请求

  • 敏感字符过滤:过滤用户输入中的非法或敏感内容

  • 日志记录与审计:记录所有请求的访问信息和耗时

  • 性能监控:统计接口响应时间,分析系统性能瓶颈

  • 跨域资源共享(CORS):统一处理跨域请求

  • 事务控制:为请求开启和提交事务,实现声明式事务

1.3 Filter 接口 API

所有自定义过滤器都必须实现 jakarta.servlet.Filter 接口,该接口定义了三个核心方法:

方法签名 执行时机 作用
default void init(FilterConfig filterConfig) Web 应用启动时,过滤器对象创建后立即执行 初始化过滤器,读取配置参数
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 每次匹配的请求到达时执行 核心过滤逻辑,决定是否放行
default void destroy() Web 应用关闭时,过滤器对象销毁前执行 释放过滤器占用的资源

接口源码(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() {}
}

二、过滤器快速入门:日志记录过滤器

我们通过一个请求日志记录过滤器来演示过滤器的完整开发流程,该过滤器将记录所有请求的访问时间、路径和处理耗时。

2.1 步骤 1:创建过滤器类

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应用启动时执行一次
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化日期格式化器
        dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("日志过滤器初始化完成");
    }

    // 核心过滤方法:每次请求都会执行
    @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应用关闭时执行一次
    @Override
    public void destroy() {
        System.out.println("日志过滤器已销毁");
    }
}

2.2 步骤 2:创建测试用 Servlet

为了验证过滤器效果,我们创建两个简单的 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 {
        // 模拟业务处理耗时
        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 {
        // 模拟业务处理耗时
        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. 配置过滤器的拦截规则 -->
    <filter-mapping>
        <filter-name>loggingFilter</filter-name>
        <!-- 拦截 /servletA 的请求 -->
        <url-pattern>/servletA</url-pattern>
        <!-- 拦截所有 .html 静态资源 -->
        <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> 匹配规则详解

过滤器的拦截范围由 <url-pattern> 标签控制,Servlet 规范定义了 4 种匹配模式,优先级从高到低排列。

3.1 四种匹配模式

  1. 精确匹配(最高优先级)

    • 写法:以 / 开头,写死完整路径

    • 示例:<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. 默认匹配(最低优先级)

    • 写法:仅写一个 /

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

    • 作用:当请求没有匹配到任何其他 Servlet 时触发,通常用于处理静态资源或 404 页面

    • 注意:与 /* 不同,/ 不会拦截 JSP 页面(JSP 有专门的内置 Servlet 处理)

3.2 匹配优先级规则

当多个 <url-pattern> 同时匹配一个 URL 时,按以下优先级选择:

  1. 精确匹配 > 路径匹配 > 扩展名匹配 > 默认匹配

  2. 路径匹配中,路径越长优先级越高(如 /user/admin/* 优先于 /user/*

3.3 常见错误写法

❌ 错误:/user/*.do(路径匹配和扩展名匹配不能混合使用)
❌ 错误:user/*(必须以 / 开头)
❌ 错误:*.do/(扩展名匹配后面不能加 /
❌ 错误:/*.*(不支持这种通配符写法)

四、过滤器生命周期

过滤器的生命周期由 Web 容器管理,与 Servlet 类似但略有不同:

生命周期阶段 对应方法 执行时机 执行次数
对象创建 构造方法 Web 应用启动时 1 次
初始化 init(FilterConfig) 构造方法执行后立即执行 1 次
请求处理 doFilter() 每次匹配的请求到达时 多次
对象销毁 destroy() Web 应用关闭时 1 次

关键特点

  • 过滤器是单例的,整个应用中只有一个实例

  • 所有请求共享同一个过滤器实例,因此要注意线程安全问题

  • 过滤器在 Web 应用启动时就会被创建和初始化,而不是第一次请求时

  • 过滤器的销毁发生在 Web 应用正常关闭时

生命周期测试代码

package com.example.filter;

import jakarta.servlet.*;
import java.io.IOException;

public class LifecycleTestFilter implements Filter {
    // 构造方法
    public LifecycleTestFilter() {
        System.out.println("1. 过滤器对象创建:构造方法执行");
    }

    // 初始化方法
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("2. 过滤器初始化:init 方法执行");
    }

    // 过滤方法
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("3. 处理请求:doFilter 方法执行");
        chain.doFilter(request, response);
    }

    // 销毁方法
    @Override
    public void destroy() {
        System.out.println("4. 过滤器销毁:destroy 方法执行");
    }
}

五、过滤器链(FilterChain)

当多个过滤器同时匹配同一个请求时,它们会按照配置顺序形成一个过滤器链。

5.1 过滤器链执行顺序

  • 请求按照 filter-mapping从上到下顺序依次经过各个过滤器

  • 响应按照相反顺序依次经过各个过滤器

  • 任何一个过滤器没有调用 chain.doFilter() 方法,请求都会被中断,不再继续传递

5.2 过滤器链示例

我们创建三个过滤器来演示执行顺序:

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 的顺序决定了过滤器的执行顺序 -->
<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>

创建测试 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 过滤器链执行顺序图解

客户端请求
    ↓
Filter1.doFilter() 前
    ↓
Filter2.doFilter() 前
    ↓
Filter3.doFilter() 前
    ↓
目标资源(Servlet)处理请求
    ↓
Filter3.doFilter() 后
    ↓
Filter2.doFilter() 后
    ↓
Filter1.doFilter() 后
    ↓
响应返回客户端

六、注解方式配置过滤器

除了 web.xml 配置外,还可以使用 @WebFilter 注解来配置过滤器,这种方式更简洁,适合现代 Web 开发。

6.1 @WebFilter 注解常用属性

属性 作用 对应 web.xml 标签
filterName 过滤器名称 <filter-name>
urlPatterns / value 拦截的 URL 模式 <url-pattern>
servletNames 拦截的 Servlet 名称 <servlet-name>
initParams 初始化参数 <init-param>
dispatcherTypes 拦截的请求分发类型 <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">(该配置会关闭注解扫描)

  • 注解配置的过滤器执行顺序无法直接控制,如果需要严格控制顺序,建议使用 web.xml 配置

  • 注解配置和 web.xml 配置可以混合使用

七、实战案例:统一中文编码处理过滤器

中文乱码是 Java Web 开发中最常见的问题之一,使用过滤器可以一次性解决全站的中文乱码问题。

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 配置

八、实战案例:登录权限验证过滤器

在实际项目中,我们通常需要保护某些资源,只有登录用户才能访问。使用过滤器可以统一实现登录验证逻辑,避免在每个 Servlet 中重复编写代码。

8.1 实现思路

  1. 定义登录页面和登录处理 Servlet

  2. 用户登录成功后,将用户信息存入 Session

  3. 创建登录验证过滤器,拦截所有受保护资源

  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. 此时再访问首页,可以正常看到欢迎信息

  4. 点击"退出登录",会销毁 Session 并返回登录页面

九、过滤器与 Servlet 对比

对比项 过滤器(Filter) Servlet
作用 对请求和响应进行预处理和后处理 处理具体的业务逻辑
执行时机 在目标资源之前执行 作为目标资源执行
能否拦截多个资源 可以,通过 url-pattern 配置 通常一个 Servlet 处理一个或一类请求
能否中断请求 可以,不调用 chain.doFilter() 即可 不能中断请求链
能否处理响应 可以,在 chain.doFilter() 后处理 只能生成响应
生命周期 Web 应用启动时创建 默认第一次请求时创建

十、最佳实践

  1. 单一职责原则:每个过滤器只负责一个功能(如编码过滤器、登录过滤器、日志过滤器)

  2. 合理使用过滤器链:按照逻辑顺序排列过滤器,编码过滤器放在最前面

  3. 避免在过滤器中执行耗时操作:过滤器会影响所有匹配请求的性能

  4. 注意线程安全:过滤器是单例的,不要在过滤器中定义可变的成员变量

  5. 合理设置拦截范围:不要盲目使用 /* 拦截所有请求,只拦截需要的资源

  6. 使用白名单机制:对于登录验证等过滤器,使用白名单排除不需要拦截的路径

  7. 及时释放资源:在 destroy() 方法中释放过滤器占用的资源

( )

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容