4. 过滤器 (Filter)

概念:Filter过滤器,是JavaWeb三大组件(Servlet、Filter、Listener)之一。

过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。

过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

CleanShot 2025-10-20 at 18.30.09@2x.png

一、Filter 基础概念

1.1 什么是 Filter?

Filter(过滤器) 是 Java Servlet 规范中的一个重要组件,用于在请求到达 Servlet 之前或响应返回客户端之前对请求和响应进行预处理和后处理。

形象比喻

可以把 Filter 想象成:

机场安检 → 你就是请求,安检就是 Filter
├─ 登机前:检查身份证、行李安检(请求预处理)
├─ 登机:到达目的地(Servlet 处理)
└─ 下机后:取行李、海关检查(响应后处理)

1.2 Filter 的作用

┌─────────────────────────────────────────────────────────┐
│                    HTTP 请求处理流程                       │
└─────────────────────────────────────────────────────────┘

客户端(浏览器)
      │
      │ HTTP 请求
      ↓
┌──────────────────┐
│   Web 服务器      │
│   (Tomcat等)     │
└──────────────────┘
      │
      ↓
┌──────────────────┐
│   Filter 1       │ ← 第一道关卡
│   (字符编码)      │
└──────────────────┘
      │
      ↓
┌──────────────────┐
│   Filter 2       │ ← 第二道关卡
│   (身份认证)      │
└──────────────────┘
      │
      ↓
┌──────────────────┐
│   Filter 3       │ ← 第三道关卡
│   (日志记录)      │
└──────────────────┘
      │
      ↓
┌──────────────────┐
│   Servlet        │ ← 最终目标
│   (业务处理)      │
└──────────────────┘
      │
      │ 处理完成
      ↓
┌──────────────────┐
│   Filter 3       │ ← 返回时再次经过
│   (响应处理)      │
└──────────────────┘
      │
      ↓
┌──────────────────┐
│   Filter 2       │
│   (响应处理)      │
└──────────────────┘
      │
      ↓
┌──────────────────┐
│   Filter 1       │
│   (响应处理)      │
└──────────────────┘
      │
      │ HTTP 响应
      ↓
客户端(浏览器)

常见应用场景:

  • ✅ 身份认证和授权
  • ✅ 日志记录
  • ✅ 字符编码设置
  • ✅ 数据压缩
  • ✅ 跨域处理(CORS)
  • ✅ XSS 防护
  • ✅ 请求参数验证
  • ✅ 性能监控
CleanShot 2025-10-20 at 18.58.46@2x.png

CleanShot 2025-10-20 at 19.03.41@2x.png

Filter 可以根据需求,配置不同的拦截资源路径:

CleanShot 2025-10-21 at 10.51.12@2x.png

1.3 Filter 接口

Filter 是一个接口,包含三个核心方法:

┌─────────────────────────────────────────┐
│          Filter 接口                     │
├─────────────────────────────────────────┤
│                                          │
│  void init(FilterConfig config)         │
│  ↓                                       │
│  初始化方法                               │
│  - 服务器启动时调用一次                    │
│  - 用于初始化资源                         │
│                                          │
│  ─────────────────────────────────       │
│                                          │
│  void doFilter(request, response, chain) │
│  ↓                                       │
│  过滤方法                                 │
│  - 每次请求都会调用                       │
│  - 核心业务逻辑                           │
│                                          │
│  ─────────────────────────────────       │
│                                          │
│  void destroy()                          │
│  ↓                                       │
│  销毁方法                                 │
│  - 服务器关闭时调用一次                    │
│  - 用于释放资源                           │
│                                          │
└─────────────────────────────────────────┘

1.4 Filter 生命周期

public interface Filter {
    // 1. 初始化(服务器启动时调用一次)
    void init(FilterConfig filterConfig) throws ServletException;
  
    // 2. 过滤处理(每次请求都会调用)
    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException;
  
    // 3. 销毁(服务器关闭时调用一次)
    void destroy();
}

Filter 生命周期流程图

┌─────────────────────────────────────────────────────────┐
│                   Filter 生命周期                         │
└─────────────────────────────────────────────────────────┘

服务器启动
    │
    ↓
┌─────────────────┐
│  创建 Filter     │
│  实例对象        │
└─────────────────┘
    │
    ↓
┌─────────────────┐
│  调用 init()     │  ← 只调用一次
│  初始化          │
└─────────────────┘
    │
    ↓
┌─────────────────┐
│  等待请求        │
└─────────────────┘
    │
    ├──→ 请求到来 ──→ ┌─────────────────┐
    │                 │ 调用 doFilter()  │ ← 每次请求都调用
    │                 │ 处理请求/响应     │
    │                 └─────────────────┘
    │                        │
    │                        ↓
    │                 ┌─────────────────┐
    │                 │  继续等待        │
    │                 │  下一个请求      │
    │                 └─────────────────┘
    │                        │
    ←────────────────────────┘
    │
    ↓
服务器关闭
    │
    ↓
┌─────────────────┐
│  调用 destroy()  │  ← 只调用一次
│  销毁资源        │
└─────────────────┘
    │
    ↓
Filter 对象被回收

1.5 doFilter 方法详解

┌─────────────────────────────────────────────────────────┐
│              doFilter 方法执行流程                         │
└─────────────────────────────────────────────────────────┘

请求到达 Filter
    │
    ↓
┌──────────────────────────────┐
│  1. 请求预处理                │
│  ─────────────────           │
│  • 读取请求信息              │
│  • 修改请求参数              │
│  • 验证请求合法性            │
│  • 记录日志                  │
└──────────────────────────────┘
    │
    ↓
┌──────────────────────────────┐
│  2. 决策点                    │
│  ─────────────────           │
│  是否放行?                   │
└──────────────────────────────┘
    │
    ├─ YES ─→ ┌──────────────────────────────┐
    │         │  3. 调用 chain.doFilter()     │
    │         │  ─────────────────           │
    │         │  • 将请求传递给下一个 Filter  │
    │         │  • 或传递给 Servlet          │
    │         └──────────────────────────────┘
    │                  │
    │                  ↓
    │         ┌──────────────────────────────┐
    │         │  4. Servlet 处理              │
    │         │  ─────────────────           │
    │         │  • 执行业务逻辑              │
    │         │  • 生成响应                  │
    │         └──────────────────────────────┘
    │                  │
    │                  ↓
    │         ┌──────────────────────────────┐
    │         │  5. 响应后处理                │
    │         │  ─────────────────           │
    │         │  • 修改响应内容              │
    │         │  • 添加响应头                │
    │         │  • 记录日志                  │
    │         └──────────────────────────────┘
    │                  │
    │                  ↓
    │         返回给客户端
    │
    └─ NO ──→ ┌──────────────────────────────┐
              │  6. 中断请求                  │
              │  ─────────────────           │
              │  • 设置错误状态码            │
              │  • 返回错误信息              │
              │  • 不调用 chain.doFilter()   │
              └──────────────────────────────┘
                      │
                      ↓
              直接返回给客户端

1.6 Filter链

1.6.1 什么是 Filter 链?

当有多个 Filter 时,它们会形成一个过滤器链,请求会依次通过每个 Filter。

1.6.2 Filter 链执行流程

┌─────────────────────────────────────────────────────────┐
│                  Filter 链执行流程                         │
└─────────────────────────────────────────────────────────┘

客户端请求
    │
    ↓
┌───────────────────────────────────────────────────────┐
│  Filter 1 (字符编码)                                    │
│  ┌─────────────────────────────────────────────────┐  │
│  │  请求前:设置字符编码为 UTF-8                      │  │
│  └─────────────────────────────────────────────────┘  │
│         │                                              │
│         ↓ chain.doFilter()                            │
└─────────┼────────────────────────────────────────────┘
          │
          ↓
┌─────────┼────────────────────────────────────────────┐
│  Filter 2 (身份认证)                                    │
│  ┌─────────────────────────────────────────────────┐  │
│  │  请求前:验证 Token 是否有效                       │  │
│  └─────────────────────────────────────────────────┘  │
│         │                                              │
│         ↓ chain.doFilter()                            │
└─────────┼────────────────────────────────────────────┘
          │
          ↓
┌─────────┼────────────────────────────────────────────┐
│  Filter 3 (日志记录)                                    │
│  ┌─────────────────────────────────────────────────┐  │
│  │  请求前:记录请求信息                              │  │
│  └─────────────────────────────────────────────────┘  │
│         │                                              │
│         ↓ chain.doFilter()                            │
└─────────┼────────────────────────────────────────────┘
          │
          ↓
┌─────────────────────────────────────────────────────┐
│  Servlet (业务处理)                                    │
│  ┌─────────────────────────────────────────────────┐│
│  │  处理业务逻辑,生成响应                            ││
│  └─────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────┘
          │
          ↓ 响应返回
┌─────────┼────────────────────────────────────────────┐
│  Filter 3 (日志记录)                                    │
│  ┌─────────────────────────────────────────────────┐  │
│  │  响应后:记录响应信息和耗时                        │  │
│  └─────────────────────────────────────────────────┘  │
└─────────┼────────────────────────────────────────────┘
          │
          ↓
┌─────────┼────────────────────────────────────────────┐
│  Filter 2 (身份认证)                                    │
│  ┌─────────────────────────────────────────────────┐  │
│  │  响应后:(通常不做处理)                          │  │
│  └─────────────────────────────────────────────────┘  │
└─────────┼────────────────────────────────────────────┘
          │
          ↓
┌─────────┼────────────────────────────────────────────┐
│  Filter 1 (字符编码)                                    │
│  ┌─────────────────────────────────────────────────┐  │
│  │  响应后:确保响应编码正确                          │  │
│  └─────────────────────────────────────────────────┘  │
└─────────┼────────────────────────────────────────────┘
          │
          ↓
    客户端收到响应

1.6.3 Filter 链的”洋葱模型”

┌─────────────────────────────────────────────────────────┐
│                  Filter 洋葱模型                          │
└─────────────────────────────────────────────────────────┘

                    ┌─────────────────┐
                    │   客户端请求     │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │    Filter 1         │                    │
        │  ┌──────────────────┼──────────────────┐ │
        │  │  Filter 2         │                  │ │
        │  │ ┌─────────────────┼────────────────┐ │ │
        │  │ │  Filter 3        │               │ │ │
        │  │ │  ┌───────────────┼─────────────┐ │ │ │
        │  │ │  │   Servlet     │             │ │ │ │
        │  │ │  │   ┌───────────▼───────────┐ │ │ │ │
        │  │ │  │   │    业务处理           │ │ │ │ │
        │  │ │  │   └───────────┬───────────┘ │ │ │ │
        │  │ │  │               │             │ │ │ │
        │  │ │  └───────────────┼─────────────┘ │ │ │
        │  │ │                  │               │ │ │
        │  │ └──────────────────┼───────────────┘ │ │
        │  │                    │                 │ │
        │  └────────────────────┼─────────────────┘ │
        │                       │                   │
        └───────────────────────┼───────────────────┘
                                │
                    ┌───────────▼────────┐
                    │   客户端响应        │
                    └────────────────────┘

说明:
• 请求从外层进入,逐层深入
• 到达 Servlet 处理业务
• 响应从内层返回,逐层退出
• 每个 Filter 都有"进入"和"退出"两个阶段

1.7 Filter 的工作原理

1.7.1 单个 Filter 的工作原理

┌─────────────────────────────────────────────────────────┐
│              单个 Filter 工作原理                          │
└─────────────────────────────────────────────────────────┘

public void doFilter(ServletRequest request, 
                     ServletResponse response, 
                     FilterChain chain) {
    
    ┌────────────────────────────────────┐
    │  第一阶段:请求预处理               │
    │  ────────────────────              │
    │  • 在 chain.doFilter() 之前执行    │
    │  • 可以修改请求                    │
    │  • 可以验证请求                    │
    └────────────────────────────────────┘
              │
              ↓
    System.out.println("请求前处理");
    String token = request.getHeader("token");
    if (token == null) {
        response.setStatus(401);
        return;  // 中断请求
    }
              │
              ↓
    ┌────────────────────────────────────┐
    │  关键点:chain.doFilter()           │
    │  ────────────────────              │
    │  • 将请求传递给下一个 Filter       │
    │  • 或传递给 Servlet                │
    │  • 必须调用才能继续                │
    └────────────────────────────────────┘
              │
              ↓
    chain.doFilter(request, response);
              │
              ↓
    ┌────────────────────────────────────┐
    │  第二阶段:响应后处理               │
    │  ────────────────────              │
    │  • 在 chain.doFilter() 之后执行    │
    │  • 可以修改响应                    │
    │  • 可以记录日志                    │
    └────────────────────────────────────┘
              │
              ↓
    System.out.println("响应后处理");
    long duration = System.currentTimeMillis() - startTime;
    System.out.println("耗时: " + duration + "ms");
}

1.7.2 多个 Filter 的协作原理

┌─────────────────────────────────────────────────────────┐
│              多个 Filter 协作原理                          │
└─────────────────────────────────────────────────────────┘

Filter1.doFilter() {
    System.out.println("Filter1 - 请求前");
    │
    ├─→ chain.doFilter()  // 调用下一个 Filter
    │   │
    │   └─→ Filter2.doFilter() {
    │           System.out.println("Filter2 - 请求前");
    │           │
    │           ├─→ chain.doFilter()  // 调用下一个 Filter
    │           │   │
    │           │   └─→ Filter3.doFilter() {
    │           │           System.out.println("Filter3 - 请求前");
    │           │           │
    │           │           ├─→ chain.doFilter()  // 调用 Servlet
    │           │           │   │
    │           │           │   └─→ Servlet.service() {
    │           │           │           System.out.println("Servlet 处理");
    │           │           │           // 业务逻辑
    │           │           │       }
    │           │           │
    │           │           System.out.println("Filter3 - 响应后");
    │           │       }
    │           │
    │           System.out.println("Filter2 - 响应后");
    │       }
    │
    System.out.println("Filter1 - 响应后");
}

执行结果:
─────────
Filter1 - 请求前
Filter2 - 请求前
Filter3 - 请求前
Servlet 处理
Filter3 - 响应后
Filter2 - 响应后
Filter1 - 响应后

1.8 Filter 的执行顺序

1.8.1 执行顺序的重要性

┌─────────────────────────────────────────────────────────┐
│              为什么执行顺序很重要?                        │
└─────────────────────────────────────────────────────────┘

错误的顺序:
───────────
请求 → 身份认证 Filter → 字符编码 Filter → Servlet
                ↑
                │
            可能读取不到正确的参数(编码问题)

正确的顺序:
───────────
请求 → 字符编码 Filter → 身份认证 Filter → Servlet
            ↑
            │
        先设置编码,再读取参数

1.8.2 指定执行顺序的方法

┌─────────────────────────────────────────────────────────┐
│              指定 Filter 执行顺序                          │
└─────────────────────────────────────────────────────────┘

方法一:使用 @Order 注解
─────────────────────

@Component
@Order(1)  ← 数字越小,优先级越高
public class Filter1 implements Filter {
    // ...
}

@Component
@Order(2)
public class Filter2 implements Filter {
    // ...
}

@Component
@Order(3)
public class Filter3 implements Filter {
    // ...
}

执行顺序:Filter1 → Filter2 → Filter3


方法二:使用 FilterRegistrationBean
──────────────────────────────

@Bean
public FilterRegistrationBean<Filter1> filter1() {
    FilterRegistrationBean<Filter1> bean = new FilterRegistrationBean<>();
    bean.setFilter(new Filter1());
    bean.setOrder(1);  ← 设置顺序
    return bean;
}

@Bean
public FilterRegistrationBean<Filter2> filter2() {
    FilterRegistrationBean<Filter2> bean = new FilterRegistrationBean<>();
    bean.setFilter(new Filter2());
    bean.setOrder(2);
    return bean;
}


方法三:实现 Ordered 接口
─────────────────────

@Component
public class Filter1 implements Filter, Ordered {
    
    @Override
    public int getOrder() {
        return 1;  ← 返回顺序值
    }
    
    @Override
    public void doFilter(...) {
        // ...
    }
}

1.8.3 执行顺序示例

┌─────────────────────────────────────────────────────────┐
│              Filter 执行顺序完整示例                       │
└─────────────────────────────────────────────────────────┘

配置的顺序:
──────────
Filter1 (Order = 1) - 字符编码
Filter2 (Order = 2) - CORS 跨域
Filter3 (Order = 3) - 身份认证
Filter4 (Order = 4) - 日志记录

实际执行流程:
────────────

客户端请求
    │
    ↓
┌─────────────────────────┐
│ Filter1 (字符编码)       │
│ ┌─────────────────────┐ │
│ │ 请求前:设置 UTF-8   │ │
│ └─────────────────────┘ │
│         ↓               │
│   chain.doFilter()      │
└─────────┼───────────────┘
          │
          ↓
┌─────────┼───────────────┐
│ Filter2 (CORS)          │
│ ┌─────────────────────┐ │
│ │ 请求前:设置 CORS 头 │ │
│ └─────────────────────┘ │
│         ↓               │
│   chain.doFilter()      │
└─────────┼───────────────┘
          │
          ↓
┌─────────┼───────────────┐
│ Filter3 (身份认证)       │
│ ┌─────────────────────┐ │
│ │ 请求前:验证 Token   │ │
│ └─────────────────────┘ │
│         ↓               │
│   chain.doFilter()      │
└─────────┼───────────────┘
          │
          ↓
┌─────────┼───────────────┐
│ Filter4 (日志)          │
│ ┌─────────────────────┐ │
│ │ 请求前:记录日志     │ │
│ └─────────────────────┘ │
│         ↓               │
│   chain.doFilter()      │
└─────────┼───────────────┘
          │
          ↓
    Servlet 处理
          │
          ↓
┌─────────┼───────────────┐
│ Filter4 (日志)          │
│ ┌─────────────────────┐ │
│ │ 响应后:记录耗时     │ │
│ └─────────────────────┘ │
└─────────┼───────────────┘
          │
          ↓
┌─────────┼───────────────┐
│ Filter3 (身份认证)       │
│ ┌─────────────────────┐ │
│ │ 响应后:(无操作)   │ │
│ └─────────────────────┘ │
└─────────┼───────────────┘
          │
          ↓
┌─────────┼───────────────┐
│ Filter2 (CORS)          │
│ ┌─────────────────────┐ │
│ │ 响应后:(无操作)   │ │
│ └─────────────────────┘ │
└─────────┼───────────────┘
          │
          ↓
┌─────────┼───────────────┐
│ Filter1 (字符编码)       │
│ ┌─────────────────────┐ │
│ │ 响应后:(无操作)   │ │
│ └─────────────────────┘ │
└─────────┼───────────────┘
          │
          ↓
    客户端响应

1.9 Filter 的常见应用场景

1.9.1 应用场景总览

┌─────────────────────────────────────────────────────────┐
│              Filter 常见应用场景                           │
└─────────────────────────────────────────────────────────┘

1. 字符编码设置
   ├─ 统一设置请求和响应的字符编码
   └─ 解决中文乱码问题

2. 跨域处理 (CORS)
   ├─ 设置 Access-Control-Allow-Origin 等响应头
   └─ 允许跨域请求

3. 身份认证
   ├─ 验证用户登录状态
   ├─ 检查 Token 是否有效
   └─ 拦截未授权的请求

4. 权限控制
   ├─ 检查用户是否有访问权限
   └─ 实现基于角色的访问控制 (RBAC)

5. 日志记录
   ├─ 记录请求信息(URL、参数、IP等)
   ├─ 记录响应信息(状态码、耗时等)
   └─ 便于问题排查和审计

6. 性能监控
   ├─ 统计接口响应时间
   ├─ 监控系统性能
   └─ 收集性能指标

7. 限流控制
   ├─ 防止恶意请求
   ├─ 保护系统资源
   └─ 实现 IP 限流、用户限流等

8. XSS 防护
   ├─ 过滤危险字符
   ├─ 防止跨站脚本攻击
   └─ 保护系统安全

9. 数据压缩
   ├─ 压缩响应数据
   └─ 减少网络传输量

10. 缓存控制
    ├─ 设置缓存策略
    └─ 提高访问速度

1.9.2 典型场景流程图

场景一:身份认证 Filter
┌─────────────────────────────────────────────────────────┐
│              身份认证 Filter 流程                          │
└─────────────────────────────────────────────────────────┘

请求到达
    │
    ↓
┌─────────────────────┐
│ 从请求头获取 Token   │
└─────────────────────┘
    │
    ↓
┌─────────────────────┐
│ Token 是否存在?     │
└─────────────────────┘
    │
    ├─ NO ──→ ┌──────────────────────┐
    │         │ 返回 401 未授权       │
    │         │ 不调用 chain.doFilter │
    │         └──────────────────────┘
    │                 │
    │                 ↓
    │         直接返回错误响应
    │
    └─ YES ─→ ┌──────────────────────┐
              │ 验证 Token 是否有效   │
              └──────────────────────┘
                      │
                      ├─ 无效 ──→ ┌──────────────────────┐
                      │           │ 返回 401 未授权       │
                      │           └──────────────────────┘
                      │
                      └─ 有效 ──→ ┌──────────────────────┐
                                  │ 从 Token 提取用户信息 │
                                  └──────────────────────┘
                                          │
                                          ↓
                                  ┌──────────────────────┐
                                  │ 设置用户信息到上下文  │
                                  └──────────────────────┘
                                          │
                                          ↓
                                  ┌──────────────────────┐
                                  │ chain.doFilter()     │
                                  │ 继续处理请求         │
                                  └──────────────────────┘

二、Filter 基本使用

2.1 传统 Servlet Filter(使用 web.xml)

2.1.1 创建 Filter 类

package com.example.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LoggingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("LoggingFilter 初始化");
        // 获取初始化参数
        String encoding = filterConfig.getInitParameter("encoding");
        System.out.println("编码参数: " + encoding);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 请求前处理
        System.out.println("请求 URL: " + req.getRequestURL());
        System.out.println("请求方法: " + req.getMethod());
        long startTime = System.currentTimeMillis();

        // 放行请求(重要!)
        chain.doFilter(request, response);

        // 响应后处理
        long endTime = System.currentTimeMillis();
        System.out.println("请求耗时: " + (endTime - startTime) + "ms");
    }

    @Override
    public void destroy() {
        System.out.println("LoggingFilter 销毁");
    }
}

2.1.2 配置 web.xml

<?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_4_0.xsd"
         version="4.0">

    <!-- 定义 Filter -->
    <filter>
        <filter-name>loggingFilter</filter-name>
        <filter-class>com.example.filter.LoggingFilter</filter-class>
        <!-- 初始化参数 -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>

    <!-- 配置 Filter 映射 -->
    <filter-mapping>
        <filter-name>loggingFilter</filter-name>
        <!-- 拦截所有请求 -->
        <url-pattern>/*</url-pattern>
        <!-- 或者拦截特定路径 -->
        <!-- <url-pattern>/api/*</url-pattern> -->
    </filter-mapping>

</web-app>

2.2 使用注解配置 Filter

package com.example.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebFilter(
    filterName = "loggingFilter",
    urlPatterns = {"/*"},                    // 拦截路径
    initParams = {
        @WebInitParam(name = "encoding", value = "UTF-8")
    }
)
public class LoggingFilter implements Filter {

    private String encoding;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        encoding = filterConfig.getInitParameter("encoding");
        System.out.println("LoggingFilter 初始化,编码: " + encoding);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
      
        System.out.println("请求 URL: " + req.getRequestURL());
      
        // 放行
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        System.out.println("LoggingFilter 销毁");
    }
}

启用注解扫描(在启动类上添加):

@SpringBootApplication
@ServletComponentScan  // 扫描 @WebFilter、@WebServlet、@WebListener
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

三、Spring Boot 中的 Filter

3.1 方式一:实现 Filter 接口 + @Component

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
@Order(1)  // 指定过滤器顺序,数字越小优先级越高
public class AuthenticationFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("AuthenticationFilter 初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        String token = req.getHeader("Authorization");
      
        if (token == null || token.isEmpty()) {
            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            resp.getWriter().write("未授权");
            return;  // 不放行
        }

        // 验证通过,放行
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        log.info("AuthenticationFilter 销毁");
    }
}

3.2 方式二:使用 FilterRegistrationBean(推荐)

package com.example.config;

import com.example.filter.AuthenticationFilter;
import com.example.filter.LoggingFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    /**
     * 注册日志过滤器
     */
    @Bean
    public FilterRegistrationBean<LoggingFilter> loggingFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
      
        registrationBean.setFilter(new LoggingFilter());
        registrationBean.addUrlPatterns("/*");           // 拦截路径
        registrationBean.setOrder(1);                    // 优先级
        registrationBean.setName("loggingFilter");       // 过滤器名称
      
        // 添加初始化参数
        registrationBean.addInitParameter("encoding", "UTF-8");
      
        return registrationBean;
    }

    /**
     * 注册认证过滤器
     */
    @Bean
    public FilterRegistrationBean<AuthenticationFilter> authenticationFilter() {
        FilterRegistrationBean<AuthenticationFilter> registrationBean = new FilterRegistrationBean<>();
      
        registrationBean.setFilter(new AuthenticationFilter());
        registrationBean.addUrlPatterns("/api/*");       // 只拦截 /api/* 路径
        registrationBean.setOrder(2);                    // 优先级低于日志过滤器
      
        return registrationBean;
    }
}

3.3 方式三:继承 OncePerRequestFilter(推荐)

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        String token = request.getHeader("Authorization");
      
        log.info("请求路径: {}, Token: {}", request.getRequestURI(), token);
      
        // 验证 Token
        if (token != null && token.startsWith("Bearer ")) {
            String jwt = token.substring(7);
            // 验证 JWT 逻辑...
        }
      
        // 放行
        filterChain.doFilter(request, response);
    }

    /**
     * 指定哪些路径不需要过滤
     */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        String path = request.getRequestURI();
        return path.startsWith("/public") || path.equals("/login");
    }
}

四、Filter 链执行顺序

4.1 多个 Filter 的执行顺序

// Filter1
@Component
@Order(1)
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
@Component
@Order(2)
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
@Component
@Order(3)
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 - 响应后");
    }
}

执行结果:

Filter1 - 请求前
Filter2 - 请求前
Filter3 - 请求前
[Servlet 处理]
Filter3 - 响应后
Filter2 - 响应后
Filter1 - 响应后

执行流程图:

客户端请求
    ↓
Filter1 (请求前)
    ↓
Filter2 (请求前)
    ↓
Filter3 (请求前)
    ↓
Servlet/Controller
    ↓
Filter3 (响应后)
    ↓
Filter2 (响应后)
    ↓
Filter1 (响应后)
    ↓
客户端响应

4.2 指定 Filter 顺序的方法

方法一:使用 @Order 注解

@Component
@Order(1)  // 数字越小,优先级越高
public class FirstFilter implements Filter {
    // ...
}

@Component
@Order(2)
public class SecondFilter implements Filter {
    // ...
}

方法二:使用 FilterRegistrationBean

@Bean
public FilterRegistrationBean<FirstFilter> firstFilter() {
    FilterRegistrationBean<FirstFilter> bean = new FilterRegistrationBean<>();
    bean.setFilter(new FirstFilter());
    bean.setOrder(1);  // 设置顺序
    return bean;
}

方法三:实现 Ordered 接口

@Component
public class FirstFilter implements Filter, Ordered {
  
    @Override
    public int getOrder() {
        return 1;  // 返回顺序值
    }
  
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // ...
    }
}

五、常见 Filter 实现示例

5.1 字符编码过滤器

package com.example.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CharacterEncodingFilter implements Filter {

    private static final String ENCODING = "UTF-8";

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 设置请求编码
        req.setCharacterEncoding(ENCODING);
      
        // 设置响应编码
        resp.setCharacterEncoding(ENCODING);
        resp.setContentType("application/json;charset=" + ENCODING);

        chain.doFilter(request, response);
    }
}

5.2 跨域(CORS)过滤器

package com.example.filter;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@Order(1)
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 允许的源
        resp.setHeader("Access-Control-Allow-Origin", "*");
      
        // 允许的方法
        resp.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
      
        // 允许的请求头
        resp.setHeader("Access-Control-Allow-Headers", 
                "Content-Type, Authorization, X-Requested-With");
      
        // 允许携带凭证
        resp.setHeader("Access-Control-Allow-Credentials", "true");
      
        // 预检请求的有效期
        resp.setHeader("Access-Control-Max-Age", "3600");

        // 处理 OPTIONS 预检请求
        if ("OPTIONS".equalsIgnoreCase(req.getMethod())) {
            resp.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        chain.doFilter(request, response);
    }
}

5.3 JWT 认证过滤器

package com.example.filter;

import com.example.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        try {
            // 1. 从请求头获取 Token
            String token = getTokenFromRequest(request);

            // 2. 验证 Token
            if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) {
                String userId = jwtUtil.getUserIdFromToken(token);
              
                // 3. 设置认证信息
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(
                                userId, null, Collections.emptyList()
                        );
              
                SecurityContextHolder.getContext().setAuthentication(authentication);
              
                log.info("用户 {} 认证成功", userId);
            }
        } catch (Exception e) {
            log.error("JWT 认证失败: {}", e.getMessage());
        }

        filterChain.doFilter(request, response);
    }

    /**
     * 从请求头中提取 Token
     */
    private String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }

    /**
     * 指定不需要过滤的路径
     */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String path = request.getRequestURI();
        return path.startsWith("/api/auth/") || 
               path.startsWith("/public/") ||
               path.equals("/login");
    }
}

5.4 请求日志过滤器

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;

@Slf4j
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        // 包装请求和响应,以便读取内容
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

        long startTime = System.currentTimeMillis();

        // 记录请求信息
        logRequest(requestWrapper);

        // 执行请求
        filterChain.doFilter(requestWrapper, responseWrapper);

        long duration = System.currentTimeMillis() - startTime;

        // 记录响应信息
        logResponse(responseWrapper, duration);

        // 将响应内容写回
        responseWrapper.copyBodyToResponse();
    }

    /**
     * 记录请求信息
     */
    private void logRequest(ContentCachingRequestWrapper request) {
        StringBuilder sb = new StringBuilder();
        sb.append("\n========== 请求信息 ==========\n");
        sb.append("请求方法: ").append(request.getMethod()).append("\n");
        sb.append("请求 URL: ").append(request.getRequestURL()).append("\n");
        sb.append("客户端 IP: ").append(getClientIp(request)).append("\n");
      
        // 记录请求头
        sb.append("请求头:\n");
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            sb.append("  ").append(headerName).append(": ")
              .append(request.getHeader(headerName)).append("\n");
        }
      
        // 记录请求参数
        sb.append("请求参数:\n");
        request.getParameterMap().forEach((key, values) -> {
            sb.append("  ").append(key).append(": ")
              .append(String.join(", ", values)).append("\n");
        });
      
        // 记录请求体
        byte[] content = request.getContentAsByteArray();
        if (content.length > 0) {
            String body = new String(content, StandardCharsets.UTF_8);
            sb.append("请求体: ").append(body).append("\n");
        }
      
        log.info(sb.toString());
    }

    /**
     * 记录响应信息
     */
    private void logResponse(ContentCachingResponseWrapper response, long duration) {
        StringBuilder sb = new StringBuilder();
        sb.append("\n========== 响应信息 ==========\n");
        sb.append("响应状态: ").append(response.getStatus()).append("\n");
        sb.append("耗时: ").append(duration).append(" ms\n");
      
        // 记录响应头
        sb.append("响应头:\n");
        response.getHeaderNames().forEach(headerName -> {
            sb.append("  ").append(headerName).append(": ")
              .append(response.getHeader(headerName)).append("\n");
        });
      
        // 记录响应体
        byte[] content = response.getContentAsByteArray();
        if (content.length > 0) {
            String body = new String(content, StandardCharsets.UTF_8);
            sb.append("响应体: ").append(body).append("\n");
        }
      
        log.info(sb.toString());
    }

    /**
     * 获取客户端真实 IP
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

5.5 XSS 防护过滤器

package com.example.filter;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class XssFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        // 包装请求,过滤 XSS
        XssHttpServletRequestWrapper wrappedRequest = new XssHttpServletRequestWrapper(request);
      
        filterChain.doFilter(wrappedRequest, response);
    }

    /**
     * 自定义请求包装类
     */
    private static class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

        public XssHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
        }

        @Override
        public String[] getParameterValues(String parameter) {
            String[] values = super.getParameterValues(parameter);
            if (values == null) {
                return null;
            }
          
            String[] encodedValues = new String[values.length];
            for (int i = 0; i < values.length; i++) {
                encodedValues[i] = cleanXSS(values[i]);
            }
            return encodedValues;
        }

        @Override
        public String getParameter(String parameter) {
            String value = super.getParameter(parameter);
            return cleanXSS(value);
        }

        @Override
        public String getHeader(String name) {
            String value = super.getHeader(name);
            return cleanXSS(value);
        }

        /**
         * 清理 XSS 攻击字符
         */
        private String cleanXSS(String value) {
            if (value == null) {
                return null;
            }
          
            // 移除脚本标签
            value = value.replaceAll("<script>(.*?)</script>", "");
            value = value.replaceAll("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", "");
            value = value.replaceAll("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", "");
            value = value.replaceAll("</script>", "");
            value = value.replaceAll("<script(.*?)>", "");
            value = value.replaceAll("eval\\((.*?)\\)", "");
            value = value.replaceAll("expression\\((.*?)\\)", "");
            value = value.replaceAll("javascript:", "");
            value = value.replaceAll("vbscript:", "");
            value = value.replaceAll("onload(.*?)=", "");
          
            return value;
        }
    }
}

5.6 限流过滤器

package com.example.filter;

import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class RateLimitFilter implements Filter {

    // 每个 IP 的限流器
    private final ConcurrentHashMap<String, RateLimiter> limiters = new ConcurrentHashMap<>();
  
    // 每秒允许的请求数
    private static final double PERMITS_PER_SECOND = 10.0;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        String clientIp = getClientIp(req);
      
        // 获取或创建限流器
        RateLimiter limiter = limiters.computeIfAbsent(
                clientIp,
                k -> RateLimiter.create(PERMITS_PER_SECOND)
        );

        // 尝试获取令牌
        if (limiter.tryAcquire()) {
            chain.doFilter(request, response);
        } else {
            log.warn("IP {} 请求过于频繁", clientIp);
            resp.setStatus(429);  // Too Many Requests
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write("{\"error\":\"请求过于频繁,请稍后再试\"}");
        }
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

六、Filter vs Interceptor vs AOP

6.1 三者对比

特性FilterInterceptorAOP
规范Servlet 规范Spring 框架Spring 框架
作用范围所有请求Spring MVC 请求方法级别
执行时机Servlet 前后Controller 前后方法前后
依赖注入不支持(需特殊配置)支持支持
访问 Spring Bean困难容易容易
粒度粗粒度中粒度细粒度

6.2 执行顺序

请求
  ↓
Filter1
  ↓
Filter2
  ↓
DispatcherServlet
  ↓
Interceptor1.preHandle()
  ↓
Interceptor2.preHandle()
  ↓
Controller Method (AOP Before)
  ↓
Controller Method 执行
↓
Controller Method (AOP After)
  ↓
Interceptor2.postHandle()
  ↓
Interceptor1.postHandle()
  ↓
视图渲染
  ↓
Interceptor2.afterCompletion()
  ↓
Interceptor1.afterCompletion()
  ↓
Filter2
  ↓
Filter1
  ↓
响应

6.3 使用场景选择

使用 Filter:

  • ✅ 字符编码设置
  • ✅ 跨域处理(CORS)
  • ✅ XSS 防护
  • ✅ 请求/响应的通用处理
  • ✅ 不依赖 Spring 的场景

使用 Interceptor:

  • ✅ 登录验证
  • ✅ 权限检查
  • ✅ 日志记录
  • ✅ 需要访问 Spring Bean
  • ✅ 需要访问 Controller 信息

使用 AOP:

  • ✅ 事务管理
  • ✅ 缓存处理
  • ✅ 方法级别的日志
  • ✅ 性能监控
  • ✅ 异常处理

七、高级特性

7.1 异步 Filter

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Slf4j
@Component
public class AsyncFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
      
        // 检查是否为异步请求
        if (req.isAsyncSupported() && !req.isAsyncStarted()) {
            log.info("开始异步处理");
          
            // 启动异步处理
            AsyncContext asyncContext = req.startAsync();
            asyncContext.setTimeout(30000);  // 30秒超时
          
            // 添加监听器
            asyncContext.addListener(new AsyncListener() {
                @Override
                public void onComplete(AsyncEvent event) {
                    log.info("异步处理完成");
                }

                @Override
                public void onTimeout(AsyncEvent event) {
                    log.warn("异步处理超时");
                }

                @Override
                public void onError(AsyncEvent event) {
                    log.error("异步处理错误", event.getThrowable());
                }

                @Override
                public void onStartAsync(AsyncEvent event) {
                    log.info("异步处理开始");
                }
            });
          
            // 在新线程中处理
            asyncContext.start(() -> {
                try {
                    // 执行耗时操作
                    Thread.sleep(5000);
                  
                    ServletResponse resp = asyncContext.getResponse();
                    resp.getWriter().write("异步处理完成");
                  
                    asyncContext.complete();
                } catch (Exception e) {
                    log.error("异步处理异常", e);
                }
            });
        } else {
            chain.doFilter(request, response);
        }
    }
}

7.2 条件过滤器

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

@Slf4j
@Component
public class ConditionalFilter extends OncePerRequestFilter {

    // 需要过滤的路径
    private static final List<String> FILTERED_PATHS = Arrays.asList(
            "/api/admin",
            "/api/user/profile",
            "/api/order"
    );

    // 排除的路径
    private static final List<String> EXCLUDED_PATHS = Arrays.asList(
            "/api/auth/login",
            "/api/auth/register",
            "/public"
    );

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        String path = request.getRequestURI();
        log.info("条件过滤器处理路径: {}", path);
      
        // 执行过滤逻辑
        // ...
      
        filterChain.doFilter(request, response);
    }

    /**
     * 判断是否需要过滤
     */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String path = request.getRequestURI();
      
        // 排除的路径不过滤
        if (EXCLUDED_PATHS.stream().anyMatch(path::startsWith)) {
            return true;
        }
      
        // 只过滤指定路径
        return FILTERED_PATHS.stream().noneMatch(path::startsWith);
    }
}

7.3 可配置的过滤器

package com.example.filter;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@Slf4j
@Component
@ConfigurationProperties(prefix = "app.filter")
@Data
public class ConfigurableFilter extends OncePerRequestFilter {

    private boolean enabled = true;
    private List<String> excludedPaths;
    private int maxRequestSize = 1024 * 1024;  // 1MB
    private long timeout = 30000;  // 30秒

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        if (!enabled) {
            filterChain.doFilter(request, response);
            return;
        }

        log.info("可配置过滤器已启用,最大请求大小: {} bytes", maxRequestSize);
      
        // 检查请求大小
        int contentLength = request.getContentLength();
        if (contentLength > maxRequestSize) {
            response.setStatus(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
            response.getWriter().write("请求体过大");
            return;
        }
      
        filterChain.doFilter(request, response);
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        if (excludedPaths == null) {
            return false;
        }
      
        String path = request.getRequestURI();
        return excludedPaths.stream().anyMatch(path::startsWith);
    }
}

配置文件:

# application.yml
app:
  filter:
    enabled: true
    excluded-paths:
      - /public
      - /api/auth
    max-request-size: 2097152  # 2MB
    timeout: 60000  # 60秒

7.4 责任链模式的 Filter

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Component
public class ChainOfResponsibilityFilter implements Filter {

    private final List<FilterHandler> handlers = new ArrayList<>();

    public ChainOfResponsibilityFilter() {
        // 添加处理器
        handlers.add(new AuthenticationHandler());
        handlers.add(new AuthorizationHandler());
        handlers.add(new ValidationHandler());
        handlers.add(new LoggingHandler());
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 执行责任链
        for (FilterHandler handler : handlers) {
            if (!handler.handle(req, resp)) {
                log.warn("请求被 {} 拒绝", handler.getClass().getSimpleName());
                return;  // 中断处理
            }
        }

        // 所有处理器都通过,继续执行
        chain.doFilter(request, response);
    }

    /**
     * 过滤器处理器接口
     */
    interface FilterHandler {
        boolean handle(HttpServletRequest request, HttpServletResponse response) throws IOException;
    }

    /**
     * 认证处理器
     */
    static class AuthenticationHandler implements FilterHandler {
        @Override
        public boolean handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
            String token = request.getHeader("Authorization");
            if (token == null || token.isEmpty()) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("未认证");
                return false;
            }
            return true;
        }
    }

    /**
     * 授权处理器
     */
    static class AuthorizationHandler implements FilterHandler {
        @Override
        public boolean handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
            // 检查权限
            String role = request.getHeader("X-User-Role");
            if (!"ADMIN".equals(role) && request.getRequestURI().startsWith("/api/admin")) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                response.getWriter().write("权限不足");
                return false;
            }
            return true;
        }
    }

    /**
     * 验证处理器
     */
    static class ValidationHandler implements FilterHandler {
        @Override
        public boolean handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
            // 验证请求参数
            return true;
        }
    }

    /**
     * 日志处理器
     */
    static class LoggingHandler implements FilterHandler {
        @Override
        public boolean handle(HttpServletRequest request, HttpServletResponse response) {
            log.info("请求路径: {}, 方法: {}", request.getRequestURI(), request.getMethod());
            return true;
        }
    }
}

八、Filter 与 Spring Security 集成

8.1 自定义 Security Filter

package com.example.security;

import com.example.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtSecurityFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        try {
            String token = extractToken(request);
          
            if (token != null && jwtUtil.validateToken(token)) {
                String userId = jwtUtil.getUserIdFromToken(token);
                String role = jwtUtil.getRoleFromToken(token);
              
                // 创建认证对象
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(
                                userId,
                                null,
                                Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role))
                        );
              
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
              
                // 设置到 SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authentication);
              
                log.debug("用户 {} 认证成功,角色: {}", userId, role);
            }
        } catch (Exception e) {
            log.error("JWT 认证失败", e);
        }

        filterChain.doFilter(request, response);
    }

    private String extractToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

8.2 Security 配置

package com.example.config;

import com.example.security.JwtSecurityFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtSecurityFilter jwtSecurityFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                    .antMatchers("/api/auth/**").permitAll()
                    .antMatchers("/public/**").permitAll()
                    .antMatchers("/api/admin/**").hasRole("ADMIN")
                    .anyRequest().authenticated()
                .and()
                // 在 UsernamePasswordAuthenticationFilter 之前添加 JWT 过滤器
                .addFilterBefore(jwtSecurityFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

九、Filter 性能优化

9.1 使用缓存减少重复计算

package com.example.filter;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class CachedValidationFilter extends OncePerRequestFilter {

    // 缓存验证结果
    private final Cache<String, Boolean> validationCache = Caffeine.newBuilder()
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .maximumSize(10000)
            .build();

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        String token = request.getHeader("Authorization");
      
        if (token != null) {
            // 先查缓存
            Boolean isValid = validationCache.getIfPresent(token);
          
            if (isValid == null) {
                // 缓存未命中,执行验证
                isValid = validateToken(token);
                validationCache.put(token, isValid);
                log.debug("Token 验证结果已缓存");
            } else {
                log.debug("使用缓存的验证结果");
            }
          
            if (!isValid) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }
        }
      
        filterChain.doFilter(request, response);
    }

    private boolean validateToken(String token) {
        // 实际的验证逻辑
        try {
            Thread.sleep(100);  // 模拟耗时操作
            return true;
        } catch (InterruptedException e) {
            return false;
        }
    }
}

9.2 异步日志记录

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class AsyncLoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        long startTime = System.currentTimeMillis();
      
        try {
            filterChain.doFilter(request, response);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
          
            // 异步记录日志,不阻塞请求
            asyncLog(request, response, duration);
        }
    }

    @Async
    public void asyncLog(HttpServletRequest request, HttpServletResponse response, long duration) {
        log.info("请求: {} {}, 状态: {}, 耗时: {}ms",
                request.getMethod(),
                request.getRequestURI(),
                response.getStatus(),
                duration);
    }
}

9.3 请求合并和批处理

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.*;

@Slf4j
@Component
public class BatchProcessingFilter implements Filter {

    private final BlockingQueue<RequestTask> requestQueue = new LinkedBlockingQueue<>(1000);
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    @Override
    public void init(FilterConfig filterConfig) {
        // 每秒批量处理一次
        scheduler.scheduleAtFixedRate(this::processBatch, 0, 1, TimeUnit.SECONDS);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
      
        // 将请求信息加入队列
        RequestTask task = new RequestTask(
                req.getRequestURI(),
                req.getMethod(),
                System.currentTimeMillis()
        );
      
        try {
            requestQueue.offer(task, 100, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            log.warn("请求队列已满");
        }
      
        chain.doFilter(request, response);
    }

    /**
     * 批量处理请求
     */
    private void processBatch() {
        if (requestQueue.isEmpty()) {
            return;
        }
      
        int batchSize = Math.min(100, requestQueue.size());
        for (int i = 0; i < batchSize; i++) {
            RequestTask task = requestQueue.poll();
            if (task != null) {
                // 批量处理逻辑(如批量写入数据库)
                log.debug("处理请求: {}", task);
            }
        }
    }

    @Override
    public void destroy() {
        scheduler.shutdown();
    }

    static class RequestTask {
        String uri;
        String method;
        long timestamp;

        public RequestTask(String uri, String method, long timestamp) {
            this.uri = uri;
            this.method = method;
            this.timestamp = timestamp;
        }
    }
}

十、Filter 测试

10.1 单元测试

package com.example.filter;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class AuthenticationFilterTest {

    @Mock
    private HttpServletRequest request;

    @Mock
    private HttpServletResponse response;

    @Mock
    private FilterChain filterChain;

    private AuthenticationFilter filter;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        filter = new AuthenticationFilter();
    }

    @Test
    void testDoFilter_WithValidToken() throws ServletException, IOException {
        // Given
        when(request.getHeader("Authorization")).thenReturn("Bearer valid-token");

        // When
        filter.doFilter(request, response, filterChain);

        // Then
        verify(filterChain).doFilter(request, response);
        verify(response, never()).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    }

    @Test
    void testDoFilter_WithoutToken() throws ServletException, IOException {
        // Given
        when(request.getHeader("Authorization")).thenReturn(null);
        StringWriter stringWriter = new StringWriter();
        PrintWriter writer = new PrintWriter(stringWriter);
        when(response.getWriter()).thenReturn(writer);

        // When
        filter.doFilter(request, response, filterChain);

        // Then
        verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        verify(filterChain, never()).doFilter(request, response);
        assertTrue(stringWriter.toString().contains("未授权"));
    }

    @Test
    void testDoFilter_WithInvalidToken() throws ServletException, IOException {
        // Given
        when(request.getHeader("Authorization")).thenReturn("Bearer invalid-token");
        StringWriter stringWriter = new StringWriter();
        PrintWriter writer = new PrintWriter(stringWriter);
        when(response.getWriter()).thenReturn(writer);

        // When
        filter.doFilter(request, response, filterChain);

        // Then
        verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    }
}

10.2 集成测试

package com.example.filter;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
class FilterIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testFilterChain_WithValidToken() throws Exception {
        mockMvc.perform(get("/api/users")
                        .header("Authorization", "Bearer valid-token"))
                .andExpect(status().isOk());
    }

    @Test
    void testFilterChain_WithoutToken() throws Exception {
        mockMvc.perform(get("/api/users"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    void testFilterChain_PublicEndpoint() throws Exception {
        mockMvc.perform(get("/public/info"))
                .andExpect(status().isOk());
    }

    @Test
    void testCorsFilter() throws Exception {
        mockMvc.perform(get("/api/users")
                        .header("Origin", "http://localhost:3000"))
                .andExpect(header().exists("Access-Control-Allow-Origin"))
                .andExpect(header().string("Access-Control-Allow-Origin", "*"));
    }
}

十一、常见问题与解决方案

11.1 Filter 中无法注入 Bean

问题:

@WebFilter("/*")
public class MyFilter implements Filter {
  
    @Autowired  // 无法注入
    private UserService userService;
}

解决方案:

方法一:使用 @Component 代替 @WebFilter

@Component
public class MyFilter implements Filter {
  
    @Autowired
    private UserService userService;  // 可以注入
}

方法二:使用 FilterRegistrationBean

@Configuration
public class FilterConfig {
  
    @Bean
    public FilterRegistrationBean<MyFilter> myFilter(UserService userService) {
        FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new MyFilter(userService));  // 通过构造函数注入
        bean.addUrlPatterns("/*");
        return bean;
    }
}

方法三:手动获取 Bean

@WebFilter("/*")
public class MyFilter implements Filter {
  
    private UserService userService;
  
    @Override
    public void init(FilterConfig filterConfig) {
        ServletContext servletContext = filterConfig.getServletContext();
        WebApplicationContext context = WebApplicationContextUtils
                .getWebApplicationContext(servletContext);
        userService = context.getBean(UserService.class);
    }
}

11.2 Filter 执行顺序问题

问题: Filter 执行顺序不符合预期

解决方案:

// 方法一:使用 @Order
@Component
@Order(1)
public class FirstFilter implements Filter { }

@Component
@Order(2)
public class SecondFilter implements Filter { }

// 方法二:使用 FilterRegistrationBean
@Bean
public FilterRegistrationBean<FirstFilter> firstFilter() {
    FilterRegistrationBean<FirstFilter> bean = new FilterRegistrationBean<>();
    bean.setFilter(new FirstFilter());
    bean.setOrder(1);
    return bean;
}

// 方法三:实现 Ordered 接口
@Component
public class FirstFilter implements Filter, Ordered {
    @Override
    public int getOrder() {
        return 1;
    }
}

11.3 Filter 重复执行

问题: Filter 被执行多次

原因:

  • 同时使用了 @Component 和 @WebFilter
  • 同时使用了 @Component 和 FilterRegistrationBean

解决方案:

// ❌ 错误:会导致 Filter 注册两次
@Component
@WebFilter("/*")
public class MyFilter implements Filter { }

// ✅ 正确:只使用一种方式
@Component
public class MyFilter implements Filter { }

// 或者
@WebFilter("/*")
public class MyFilter implements Filter { }
// 并在启动类添加 @ServletComponentScan

11.4 Filter 中获取请求体多次

问题: 请求体只能读取一次

解决方案:

package com.example.wrapper;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * 可重复读取的请求包装类
 */
public class RepeatableReadRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] body;

    public RepeatableReadRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 读取请求体并缓存
        body = request.getInputStream().readAllBytes();
    }

    @Override
    public ServletInputStream getInputStream() {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
      
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return byteArrayInputStream.available() == 0;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener listener) {
                throw new UnsupportedOperationException();
            }

            @Override
            public int read() {
                return byteArrayInputStream.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    public String getBody() {
        return new String(body);
    }
}

在 Filter 中使用:

@Component
public class BodyCachingFilter implements Filter {
  
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
      
        // 包装请求
        RepeatableReadRequestWrapper wrapper = new RepeatableReadRequestWrapper(req);
      
        // 可以多次读取请求体
        String body1 = wrapper.getBody();
        String body2 = wrapper.getBody();
      
        chain.doFilter(wrapper, response);
    }
}

十二、最佳实践总结

12.1 Filter 设计原则

  1. 单一职责原则

    • 每个 Filter 只负责一个功能
    • 避免在一个 Filter 中处理多个不相关的逻辑
  2. 最小化原则

    • 只在必要的路径上应用 Filter
    • 使用 shouldNotFilter() 排除不需要过滤的请求
  3. 性能优先原则

    • 避免在 Filter 中执行耗时操作
    • 使用缓存减少重复计算
    • 考虑异步处理日志等非关键操作
  4. 异常处理原则

    • 必须妥善处理异常,避免影响请求链
    • 记录详细的错误日志
    • 提供友好的错误响应

12.2 Filter 开发规范

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Filter 开发规范示例
 * 
 * 1. 使用 OncePerRequestFilter 确保每个请求只执行一次
 * 2. 使用 @Slf4j 记录日志
 * 3. 使用 @Order 明确指定执行顺序
 * 4. 实现 shouldNotFilter() 排除不需要过滤的路径
 * 5. 妥善处理异常
 * 6. 添加详细的注释
 */
@Slf4j
@Component
@Order(10)
public class StandardFilter extends OncePerRequestFilter {

    /**
     * 核心过滤逻辑
     */
    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        long startTime = System.currentTimeMillis();
        String requestId = generateRequestId();
      
        try {
            // 1. 请求前处理
            log.info("[{}] 请求开始: {} {}", requestId, request.getMethod(), request.getRequestURI());
          
            // 2. 设置请求属性(供后续使用)
            request.setAttribute("requestId", requestId);
            request.setAttribute("startTime", startTime);
          
            // 3. 执行过滤链
            filterChain.doFilter(request, response);
          
            // 4. 响应后处理
            long duration = System.currentTimeMillis() - startTime;
            log.info("[{}] 请求完成: 状态={}, 耗时={}ms", 
                    requestId, response.getStatus(), duration);
          
        } catch (Exception e) {
            // 5. 异常处理
            log.error("[{}] 请求异常", requestId, e);
            handleException(response, e);
        }
    }

    /**
     * 判断是否需要过滤
     */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String path = request.getRequestURI();
      
        // 排除静态资源
        if (path.startsWith("/static/") || 
            path.startsWith("/public/") ||
            path.endsWith(".css") ||
            path.endsWith(".js") ||
            path.endsWith(".jpg") ||
            path.endsWith(".png")) {
            return true;
        }
      
        // 排除健康检查
        if (path.equals("/actuator/health")) {
            return true;
        }
      
        return false;
    }

    /**
     * 生成请求ID
     */
    private String generateRequestId() {
        return "REQ-" + System.currentTimeMillis() + "-" + 
               Thread.currentThread().getId();
    }

    /**
     * 处理异常
     */
    private void handleException(HttpServletResponse response, Exception e) throws IOException {
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write("{\"error\":\"服务器内部错误\"}");
    }
}

12.3 常用 Filter 配置模板

package com.example.config;

import com.example.filter.*;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Filter 统一配置类
 */
@Configuration
public class FilterConfiguration {

    /**
     * 字符编码过滤器(优先级最高)
     */
    @Bean
    public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
        FilterRegistrationBean<CharacterEncodingFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new CharacterEncodingFilter());
        bean.addUrlPatterns("/*");
        bean.setOrder(1);
        bean.setName("characterEncodingFilter");
        return bean;
    }

    /**
     * CORS 跨域过滤器
     */
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new CorsFilter());
        bean.addUrlPatterns("/*");
        bean.setOrder(2);
        bean.setName("corsFilter");
        return bean;
    }

    /**
     * XSS 防护过滤器
     */
    @Bean
    public FilterRegistrationBean<XssFilter> xssFilter() {
        FilterRegistrationBean<XssFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new XssFilter());
        bean.addUrlPatterns("/*");
        bean.setOrder(3);
        bean.setName("xssFilter");
        return bean;
    }

    /**
     * JWT 认证过滤器
     */
    @Bean
    public FilterRegistrationBean<JwtAuthenticationFilter> jwtAuthenticationFilter(
            JwtAuthenticationFilter filter) {
        FilterRegistrationBean<JwtAuthenticationFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(filter);
        bean.addUrlPatterns("/api/*");  // 只拦截 API 请求
        bean.setOrder(4);
        bean.setName("jwtAuthenticationFilter");
        return bean;
    }

    /**
     * 请求日志过滤器
     */
    @Bean
    public FilterRegistrationBean<RequestLoggingFilter> requestLoggingFilter() {
        FilterRegistrationBean<RequestLoggingFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new RequestLoggingFilter());
        bean.addUrlPatterns("/*");
        bean.setOrder(5);
        bean.setName("requestLoggingFilter");
        return bean;
    }

    /**
     * 限流过滤器
     */
    @Bean
    public FilterRegistrationBean<RateLimitFilter> rateLimitFilter() {
        FilterRegistrationBean<RateLimitFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new RateLimitFilter());
        bean.addUrlPatterns("/api/*");
        bean.setOrder(6);
        bean.setName("rateLimitFilter");
        return bean;
    }
}

十三、Filter 监控与调试

13.1 Filter 性能监控

package com.example.filter;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Filter 性能监控
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class MetricsFilter extends OncePerRequestFilter {

    private final MeterRegistry meterRegistry;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        String path = request.getRequestURI();
        String method = request.getMethod();
      
        // 使用 Micrometer 记录请求耗时
        Timer.Sample sample = Timer.start(meterRegistry);
      
        try {
            filterChain.doFilter(request, response);
        } finally {
            sample.stop(Timer.builder("http.server.requests")
                    .tag("method", method)
                    .tag("uri", path)
                    .tag("status", String.valueOf(response.getStatus()))
                    .register(meterRegistry));
          
            // 记录请求计数
            meterRegistry.counter("http.requests.total",
                    "method", method,
                    "uri", path,
                    "status", String.valueOf(response.getStatus())
            ).increment();
        }
    }
}

13.2 Filter 调试工具

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

/**
 * Filter 调试工具
 * 只在开发环境启用
 */
@Slf4j
@Component
@ConditionalOnProperty(name = "app.debug.filter", havingValue = "true")
public class DebugFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        log.debug("========== Filter 调试信息 ==========");
        log.debug("请求 URL: {}", request.getRequestURL());
        log.debug("请求方法: {}", request.getMethod());
        log.debug("客户端 IP: {}", getClientIp(request));
        log.debug("Content-Type: {}", request.getContentType());
        log.debug("Content-Length: {}", request.getContentLength());
      
        // 打印所有请求头
        log.debug("请求头:");
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            log.debug("  {}: {}", headerName, request.getHeader(headerName));
        }
      
        // 打印所有请求参数
        log.debug("请求参数:");
        request.getParameterMap().forEach((key, values) -> {
            log.debug("  {}: {}", key, String.join(", ", values));
        });
      
        // 打印所有属性
        log.debug("请求属性:");
        Enumeration<String> attributeNames = request.getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            String attrName = attributeNames.nextElement();
            log.debug("  {}: {}", attrName, request.getAttribute(attrName));
        }
      
        long startTime = System.currentTimeMillis();
      
        filterChain.doFilter(request, response);
      
        long duration = System.currentTimeMillis() - startTime;
        log.debug("响应状态: {}", response.getStatus());
        log.debug("请求耗时: {}ms", duration);
        log.debug("=====================================");
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

配置文件:

# application-dev.yml
app:
  debug:
    filter: true  # 开发环境启用 Filter 调试

13.3 Filter 链路追踪

package com.example.filter;

import brave.Span;
import brave.Tracer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Filter 链路追踪(集成 Sleuth)
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TracingFilter extends OncePerRequestFilter {

    private final Tracer tracer;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        // 创建新的 Span
        Span span = tracer.nextSpan().name("filter-processing").start();
      
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            // 添加标签
            span.tag("http.method", request.getMethod());
            span.tag("http.url", request.getRequestURI());
            span.tag("client.ip", getClientIp(request));
          
            // 记录事件
            span.annotate("filter.start");
          
            filterChain.doFilter(request, response);
          
            span.annotate("filter.end");
            span.tag("http.status", String.valueOf(response.getStatus()));
          
        } catch (Exception e) {
            span.error(e);
            throw e;
        } finally {
            span.finish();
        }
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        return ip != null ? ip : request.getRemoteAddr();
    }
}

十四、实战案例

14.1 完整的 API 网关 Filter

package com.example.filter;

import com.example.service.ApiKeyService;
import com.example.service.RateLimitService;
import com.example.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * API 网关过滤器
 * 集成认证、授权、限流、日志等功能
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ApiGatewayFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;
    private final ApiKeyService apiKeyService;
    private final RateLimitService rateLimitService;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        String requestId = generateRequestId();
        long startTime = System.currentTimeMillis();
      
        try {
            // 1. 记录请求日志
            logRequest(request, requestId);
          
            // 2. 验证 API Key(如果有)
            if (!validateApiKey(request, response)) {
                return;
            }
          
            // 3. 验证 JWT Token
            if (!validateJwtToken(request, response)) {
                return;
            }
          
            // 4. 检查限流
            if (!checkRateLimit(request, response)) {
                return;
            }
          
            // 5. 设置请求属性
            request.setAttribute("requestId", requestId);
            request.setAttribute("startTime", startTime);
          
            // 6. 执行请求
            filterChain.doFilter(request, response);
          
            // 7. 记录响应日志
            logResponse(request, response, requestId, startTime);
          
        } catch (Exception e) {
            log.error("[{}] API 网关异常", requestId, e);
            handleError(response, e);
        }
    }

    /**
     * 验证 API Key
     */
    private boolean validateApiKey(HttpServletRequest request, HttpServletResponse response) 
            throws IOException {
        String apiKey = request.getHeader("X-API-Key");
      
        if (apiKey != null && !apiKeyService.isValid(apiKey)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"error\":\"Invalid API Key\"}");
            return false;
        }
      
        return true;
    }

    /**
     * 验证 JWT Token
     */
    private boolean validateJwtToken(HttpServletRequest request, HttpServletResponse response) 
            throws IOException {
        String token = extractToken(request);
      
        if (token != null && !jwtUtil.validateToken(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"error\":\"Invalid or expired token\"}");
            return false;
        }
      
        return true;
    }

    /**
     * 检查限流
     */
    private boolean checkRateLimit(HttpServletRequest request, HttpServletResponse response) 
            throws IOException {
        String clientIp = getClientIp(request);
      
        if (!rateLimitService.allowRequest(clientIp)) {
            response.setStatus(429);  // Too Many Requests
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"error\":\"Rate limit exceeded\"}");
            return false;
        }
      
        return true;
    }

    /**
     * 记录请求日志
     */
    private void logRequest(HttpServletRequest request, String requestId) {
        log.info("[{}] API 请求: {} {} from {}", 
                requestId,
                request.getMethod(),
                request.getRequestURI(),
                getClientIp(request));
    }

    /**
     * 记录响应日志
     */
    private void logResponse(HttpServletRequest request, HttpServletResponse response, 
                            String requestId, long startTime) {
        long duration = System.currentTimeMillis() - startTime;
        log.info("[{}] API 响应: 状态={}, 耗时={}ms", 
                requestId, response.getStatus(), duration);
    }

    /**
     * 处理错误
     */
    private void handleError(HttpServletResponse response, Exception e) throws IOException {
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write("{\"error\":\"Internal server error\"}");
    }

    /**
     * 提取 Token
     */
    private String extractToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }

    /**
     * 获取客户端 IP
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 生成请求 ID
     */
    private String generateRequestId() {
        return "API-" + System.currentTimeMillis() + "-" + 
               Thread.currentThread().getId();
    }

    /**
     * 排除不需要过滤的路径
     */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String path = request.getRequestURI();
        return path.startsWith("/public/") || 
               path.equals("/health") ||
               path.equals("/api/auth/login");
    }
}

14.2 多租户 Filter

package com.example.filter;

import com.example.context.TenantContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 多租户过滤器
 * 从请求头中提取租户ID并设置到上下文
 */
@Slf4j
@Component
public class TenantFilter extends OncePerRequestFilter {

    private static final String TENANT_HEADER = "X-Tenant-Id";

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        try {
            // 1. 从请求头获取租户ID
            String tenantId = request.getHeader(TENANT_HEADER);
          
            if (tenantId == null || tenantId.isEmpty()) {
                log.warn("请求缺少租户ID: {}", request.getRequestURI());
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                response.getWriter().write("{\"error\":\"Missing tenant ID\"}");
                return;
            }
          
            // 2. 设置到租户上下文
            TenantContext.setTenantId(tenantId);
            log.debug("设置租户ID: {}", tenantId);
          
            // 3. 执行请求
            filterChain.doFilter(request, response);
          
        } finally {
            // 4. 清理租户上下文
            TenantContext.clear();
        }
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String path = request.getRequestURI();
        // 公共接口不需要租户ID
        return path.startsWith("/public/") || path.equals("/health");
    }
}

租户上下文:

package com.example.context;

/**
 * 租户上下文
 * 使用 ThreadLocal 存储当前租户ID
 */
public class TenantContext {

    private static final ThreadLocal<String> TENANT_ID = new ThreadLocal<>();

    public static void setTenantId(String tenantId) {
        TENANT_ID.set(tenantId);
    }

    public static String getTenantId() {
        return TENANT_ID.get();
    }

    public static void clear() {
        TENANT_ID.remove();
    }
}

14.3 请求签名验证 Filter

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;

/**
 * 请求签名验证过滤器
 * 防止请求被篡改
 */
@Slf4j
@Component
public class SignatureFilter extends OncePerRequestFilter {

    private static final String SIGNATURE_HEADER = "X-Signature";
    private static final String TIMESTAMP_HEADER = "X-Timestamp";
    private static final String SECRET_KEY = "your-secret-key";
    private static final long MAX_TIME_DIFF = 5 * 60 * 1000;  // 5分钟

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        // 1. 获取签名和时间戳
        String signature = request.getHeader(SIGNATURE_HEADER);
        String timestamp = request.getHeader(TIMESTAMP_HEADER);
      
        if (signature == null || timestamp == null) {
            log.warn("请求缺少签名或时间戳");
            sendError(response, "Missing signature or timestamp");
            return;
        }
      
        // 2. 验证时间戳(防重放攻击)
        try {
            long requestTime = Long.parseLong(timestamp);
            long currentTime = System.currentTimeMillis();
          
            if (Math.abs(currentTime - requestTime) > MAX_TIME_DIFF) {
                log.warn("请求时间戳过期: {}", timestamp);
                sendError(response, "Request timestamp expired");
                return;
            }
        } catch (NumberFormatException e) {
            log.warn("无效的时间戳格式: {}", timestamp);
            sendError(response, "Invalid timestamp format");
            return;
        }
      
        // 3. 计算签名
        String expectedSignature = calculateSignature(request, timestamp);
      
        // 4. 验证签名
        if (!signature.equals(expectedSignature)) {
            log.warn("签名验证失败");
            sendError(response, "Invalid signature");
            return;
        }
      
        log.debug("签名验证成功");
        filterChain.doFilter(request, response);
    }

    /**
     * 计算签名
     * 签名算法: Base64(SHA256(method + uri + timestamp + secretKey))
     */
    private String calculateSignature(HttpServletRequest request, String timestamp) {
        try {
            String data = request.getMethod() + 
                         request.getRequestURI() + 
                         timestamp + 
                         SECRET_KEY;
          
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
          
            return Base64.getEncoder().encodeToString(hash);
        } catch (Exception e) {
            log.error("计算签名失败", e);
            return "";
        }
    }

    /**
     * 发送错误响应
     */
    private void sendError(HttpServletResponse response, String message) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write("{\"error\":\"" + message + "\"}");
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String path = request.getRequestURI();
        // 公共接口不需要签名
        return path.startsWith("/public/") || path.equals("/health");
    }
}

十五、Filter 完整项目结构

src/main/java/com/example/
├── filter/
│   ├── core/
│   │   ├── CharacterEncodingFilter.java      # 字符编码
│   │   ├── CorsFilter.java                    # 跨域处理
│   │   └── XssFilter.java                     # XSS 防护
│   ├── security/
│   │   ├── JwtAuthenticationFilter.java       # JWT 认证
│   │   ├── SignatureFilter.java               # 请求签名
│   │   └── ApiKeyFilter.java                  # API Key 验证
│   ├── monitoring/
│   │   ├── RequestLoggingFilter.java          # 请求日志
│   │   ├── MetricsFilter.java                 # 性能监控
│   │   └── TracingFilter.java                 # 链路追踪
│   ├── business/
│   │   ├── TenantFilter.java                  # 多租户
│   │   ├── RateLimitFilter.java               # 限流
│   │   └── ApiGatewayFilter.java              # API 网关
│   └── debug/
│       └── DebugFilter.java                   # 调试工具
├── config/
│   └── FilterConfiguration.java               # Filter 配置
├── wrapper/
│   ├── RepeatableReadRequestWrapper.java      # 可重复读取请求
│   └── XssHttpServletRequestWrapper.java      # XSS 防护包装
├── context/
│   └── TenantContext.java                     # 租户上下文
└── util/
    ├── JwtUtil.java                           # JWT 工具
    └── SignatureUtil.java                     # 签名工具

十六、总结

16.1 Filter 核心要点

  1. 生命周期

    • init() – 初始化(服务器启动时)
    • doFilter() – 过滤处理(每次请求)
    • destroy() – 销毁(服务器关闭时)
  2. 执行顺序

    • 使用 @OrderFilterRegistrationBean.setOrder() 或实现 Ordered 接口
    • 数字越小,优先级越高
  3. 放行机制

    • 必须调用 chain.doFilter(request, response) 才能继续执行
    • 不调用则中断请求链
  4. 适用场景

    • 字符编码、跨域、XSS 防护
    • 认证、授权、限流
    • 日志记录、性能监控
    • 请求/响应的通用处理

16.2 最佳实践清单

  • ✅ 继承 OncePerRequestFilter 避免重复执行
  • ✅ 使用 shouldNotFilter() 排除不需要过滤的路径
  • ✅ 妥善处理异常,避免影响请求链
  • ✅ 使用 @Order 明确指定执行顺序
  • ✅ 避免在 Filter 中执行耗时操作
  • ✅ 使用缓存减少重复计算
  • ✅ 记录详细的日志便于调试
  • ✅ 单一职责,每个 Filter 只做一件事
  • ✅ 使用 FilterRegistrationBean 进行灵活配置
  • ✅ 考虑使用异步处理非关键操作

16.3 常见错误避免

错误说明解决方案
忘记调用 chain.doFilter()请求被中断确保在需要放行时调用
同时使用多种注册方式Filter 重复执行只使用一种注册方式
在 @WebFilter 中注入 Bean注入失败使用 @Component 或 FilterRegistrationBean
多次读取请求体流只能读取一次使用 RequestWrapper 缓存请求体
Filter 中执行耗时操作影响性能使用异步处理或缓存
未处理异常导致请求失败添加 try-catch 并记录日志

16.4 Filter vs Interceptor vs AOP 选择指南

┌─────────────────────────────────────────────────────────┐
│                     请求处理流程                          │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  客户端请求                                               │
│      ↓                                                   │
│  Filter (Servlet 规范)                                   │
│      ├─ 字符编码                                          │
│      ├─ 跨域处理                                          │
│      ├─ XSS 防护                                          │
│      └─ 请求/响应通用处理                                  │
│      ↓                                                   │
│  DispatcherServlet                                       │
│      ↓                                                   │
│  Interceptor (Spring MVC)                                │
│      ├─ 登录验证                                          │
│      ├─ 权限检查                                          │
│      ├─ 日志记录                                          │
│      └─ 访问 Controller 信息                              │
│      ↓                                                   │
│  Controller                                              │
│      ↓                                                   │
│  AOP (Spring)                                            │
│      ├─ 事务管理                                          │
│      ├─ 缓存处理                                          │
│      ├─ 方法级日志                                         │
│      └─ 异常处理                                          │
│      ↓                                                   │
│  Service                                                 │
│      ↓                                                   │
│  响应返回                                                 │
│                                                          │
└─────────────────────────────────────────────────────────┘

选择建议:

  1. 使用 Filter 的场景:

    • 需要在 Servlet 容器级别处理
    • 不依赖 Spring 容器
    • 处理所有类型的请求(包括静态资源)
    • 字符编码、跨域、XSS 防护等
  2. 使用 Interceptor 的场景:

    • 需要访问 Spring Bean
    • 需要访问 Controller 的信息
    • 只处理 Spring MVC 的请求
    • 登录验证、权限检查等
  3. 使用 AOP 的场景:

    • 方法级别的横切关注点
    • 需要访问方法参数和返回值
    • 事务管理、缓存、日志等

十七、高级主题

17.1 Filter 与响应式编程

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

/**
 * WebFlux 响应式 Filter
 * 用于 Spring WebFlux 项目
 */
@Slf4j
@Component
public class ReactiveLoggingFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        long startTime = System.currentTimeMillis();
        String path = exchange.getRequest().getPath().value();
        String method = exchange.getRequest().getMethod().name();
      
        log.info("请求开始: {} {}", method, path);
      
        return chain.filter(exchange)
                .doFinally(signalType -> {
                    long duration = System.currentTimeMillis() - startTime;
                    int statusCode = exchange.getResponse().getStatusCode().value();
                    log.info("请求完成: {} {}, 状态: {}, 耗时: {}ms", 
                            method, path, statusCode, duration);
                });
    }
}

17.2 Filter 与 Servlet 3.0+ 异步支持

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;

/**
 * 支持异步 Servlet 的 Filter
 */
@Slf4j
@Component
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class AsyncSupportFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
      
        if (req.isAsyncSupported()) {
            log.info("请求支持异步处理");
          
            // 启动异步上下文
            AsyncContext asyncContext = req.startAsync();
            asyncContext.setTimeout(30000);
          
            // 异步处理
            CompletableFuture.runAsync(() -> {
                try {
                    // 执行耗时操作
                    Thread.sleep(2000);
                  
                    // 继续过滤链
                    chain.doFilter(asyncContext.getRequest(), asyncContext.getResponse());
                  
                    // 完成异步处理
                    asyncContext.complete();
                } catch (Exception e) {
                    log.error("异步处理失败", e);
                }
            });
        } else {
            // 同步处理
            chain.doFilter(request, response);
        }
    }
}

17.3 Filter 与 Servlet 容器集成

package com.example.config;

import com.example.filter.CustomFilter;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.DispatcherType;
import java.util.EnumSet;

/**
 * Servlet 容器自定义配置
 */
@Configuration
public class ServletContainerConfig {

    /**
     * 自定义 Tomcat 配置
     */
    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
        return factory -> {
            // 添加 Filter
            factory.addInitializers(servletContext -> {
                javax.servlet.FilterRegistration.Dynamic filter = 
                        servletContext.addFilter("customFilter", new CustomFilter());
              
                // 配置 Filter
                filter.addMappingForUrlPatterns(
                        EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD),
                        false,
                        "/*"
                );
              
                // 设置初始化参数
                filter.setInitParameter("encoding", "UTF-8");
              
                // 支持异步
                filter.setAsyncSupported(true);
            });
          
            // 其他 Tomcat 配置
            factory.setPort(8080);
            factory.setContextPath("/api");
        };
    }
}

17.4 Filter 与分布式追踪

package com.example.filter;

import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMapAdapter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 分布式追踪 Filter(集成 OpenTracing)
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class DistributedTracingFilter extends OncePerRequestFilter {

    private final Tracer tracer;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
      
        // 1. 从请求头中提取 Span Context
        Map<String, String> headers = extractHeaders(request);
        Span span = tracer.buildSpan("http-request")
                .asChildOf(tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(headers)))
                .start();
      
        try {
            // 2. 设置标签
            span.setTag("http.method", request.getMethod());
            span.setTag("http.url", request.getRequestURL().toString());
            span.setTag("component", "servlet-filter");
          
            // 3. 将 Span 注入到请求属性中
            request.setAttribute("span", span);
          
            // 4. 执行请求
            filterChain.doFilter(request, response);
          
            // 5. 设置响应标签
            span.setTag("http.status_code", response.getStatus());
          
        } catch (Exception e) {
            // 6. 记录错误
            span.setTag("error", true);
            span.log(Map.of("event", "error", "message", e.getMessage()));
            throw e;
        } finally {
            // 7. 完成 Span
            span.finish();
        }
    }

    /**
     * 提取请求头
     */
    private Map<String, String> extractHeaders(HttpServletRequest request) {
        Map<String, String> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
      
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
      
        return headers;
    }
}

十八、实用工具类

18.1 Filter 工具类

package com.example.util;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * Filter 工具类
 */
public class FilterUtils {

    /**
     * 获取客户端真实 IP
     */
    public static String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
      
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
      
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
      
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
      
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
      
        // 处理多个 IP 的情况(取第一个)
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
      
        return ip;
    }

    /**
     * 获取所有请求头
     */
    public static Map<String, String> getHeaders(HttpServletRequest request) {
        Map<String, String> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
      
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
      
        return headers;
    }

    /**
     * 获取所有请求参数
     */
    public static Map<String, String[]> getParameters(HttpServletRequest request) {
        return new HashMap<>(request.getParameterMap());
    }

    /**
     * 读取请求体
     */
    public static String getRequestBody(HttpServletRequest request) throws IOException {
        StringBuilder body = new StringBuilder();
      
        try (BufferedReader reader = request.getReader()) {
            String line;
            while ((line = reader.readLine()) != null) {
                body.append(line);
            }
        }
      
        return body.toString();
    }

    /**
     * 判断是否为 AJAX 请求
     */
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String requestedWith = request.getHeader("X-Requested-With");
        return "XMLHttpRequest".equalsIgnoreCase(requestedWith);
    }

    /**
     * 判断是否为移动端请求
     */
    public static boolean isMobileRequest(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        if (userAgent == null) {
            return false;
        }
      
        userAgent = userAgent.toLowerCase();
        return userAgent.contains("mobile") || 
               userAgent.contains("android") || 
               userAgent.contains("iphone") || 
               userAgent.contains("ipad");
    }

    /**
     * 获取请求的完整 URL
     */
    public static String getFullUrl(HttpServletRequest request) {
        StringBuffer url = request.getRequestURL();
        String queryString = request.getQueryString();
      
        if (queryString != null && !queryString.isEmpty()) {
            url.append("?").append(queryString);
        }
      
        return url.toString();
    }

    /**
     * 判断是否为静态资源请求
     */
    public static boolean isStaticResource(HttpServletRequest request) {
        String uri = request.getRequestURI();
        String[] staticExtensions = {".css", ".js", ".jpg", ".jpeg", ".png", 
                                     ".gif", ".ico", ".svg", ".woff", ".ttf"};
      
        for (String ext : staticExtensions) {
            if (uri.endsWith(ext)) {
                return true;
            }
        }
      
        return false;
    }
}

18.2 响应包装工具类

package com.example.wrapper;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

/**
 * 可缓存响应的包装类
 */
public class CachedResponseWrapper extends HttpServletResponseWrapper {

    private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    private final PrintWriter writer;
    private ServletOutputStream servletOutputStream;

    public CachedResponseWrapper(HttpServletResponse response) throws IOException {
        super(response);
        this.writer = new PrintWriter(new OutputStreamWriter(outputStream, 
                response.getCharacterEncoding()));
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (servletOutputStream == null) {
            servletOutputStream = new ServletOutputStream() {
                @Override
                public boolean isReady() {
                    return true;
                }

                @Override
                public void setWriteListener(WriteListener listener) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public void write(int b) throws IOException {
                    outputStream.write(b);
                }
            };
        }
        return servletOutputStream;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return writer;
    }

    /**
     * 获取响应内容
     */
    public byte[] getContent() {
        writer.flush();
        return outputStream.toByteArray();
    }

    /**
     * 获取响应内容(字符串)
     */
    public String getContentAsString() {
        return new String(getContent());
    }

    /**
     * 将缓存的内容写回原始响应
     */
    public void copyBodyToResponse() throws IOException {
        byte[] content = getContent();
        if (content.length > 0) {
            HttpServletResponse response = (HttpServletResponse) getResponse();
            response.setContentLength(content.length);
            response.getOutputStream().write(content);
        }
    }
}

十九、性能优化技巧

19.1 使用对象池减少对象创建

package com.example.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * 使用对象池优化 Filter
 */
@Slf4j
@Component
public class PooledFilter implements Filter {

    private GenericObjectPool<RequestProcessor> processorPool;

    @Override
    public void init(FilterConfig filterConfig) {
        // 初始化对象池
        processorPool = new GenericObjectPool<>(new RequestProcessorFactory());
        processorPool.setMaxTotal(100);
        processorPool.setMaxIdle(20);
        processorPool.setMinIdle(5);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
        RequestProcessor processor = null;
      
        try {
            // 从池中获取处理器
            processor = processorPool.borrowObject();
          
            // 处理请求
            processor.process(req);
          
            chain.doFilter(request, response);
          
        } catch (Exception e) {
            log.error("请求处理失败", e);
        } finally {
            // 归还到池中
            if (processor != null) {
                try {
                    processorPool.returnObject(processor);
                } catch (Exception e) {
                    log.error("归还对象失败", e);
                }
            }
        }
    }

    @Override
    public void destroy() {
        if (processorPool != null) {
            processorPool.close();
        }
    }

    /**
     * 请求处理器
     */
    static class RequestProcessor {
        public void process(HttpServletRequest request) {
            // 处理逻辑
            log.debug("处理请求: {}", request.getRequestURI());
        }
    }

    /**
     * 对象池工厂
     */
    static class RequestProcessorFactory extends BasePooledObjectFactory<RequestProcessor> {
        @Override
        public RequestProcessor create() {
            return new RequestProcessor();
        }

        @Override
        public PooledObject<RequestProcessor> wrap(RequestProcessor processor) {
            return new DefaultPooledObject<>(processor);
        }
    }
}

19.2 使用布隆过滤器优化黑名单检查

package com.example.filter;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * 使用布隆过滤器优化黑名单检查
 */
@Slf4j
@Component
public class BloomFilterBlacklistFilter implements Filter {

    private BloomFilter<String> blacklistFilter;

    @Override
    public void init(FilterConfig filterConfig) {
        // 初始化布隆过滤器(预期100万个元素,误判率0.01)
        blacklistFilter = BloomFilter.create(
                Funnels.stringFunnel(StandardCharsets.UTF_8),
                1000000,
                0.01
        );
      
        // 加载黑名单IP
        loadBlacklist();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
      
        String clientIp = getClientIp(req);
      
        // 快速检查是否在黑名单中
        if (blacklistFilter.mightContain(clientIp)) {
            log.warn("黑名单IP访问: {}", clientIp);
            resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
            resp.getWriter().write("Access denied");
            return;
        }
      
        chain.doFilter(request, response);
    }

    /**
     * 加载黑名单
     */
    private void loadBlacklist() {
        // 从数据库或配置文件加载黑名单IP
        String[] blacklistIps = {
                "192.168.1.100",
                "10.0.0.50",
                // ... 更多IP
        };
      
        for (String ip : blacklistIps) {
            blacklistFilter.put(ip);
        }
      
        log.info("已加载 {} 个黑名单IP", blacklistIps.length);
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        return ip != null ? ip : request.getRemoteAddr();
    }
}

19.3 使用本地缓存减少数据库查询

package com.example.filter;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * 使用本地缓存优化用户权限检查
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CachedAuthorizationFilter implements Filter {

    // 用户权限缓存
    private LoadingCache<String, UserPermissions> permissionsCache;

    @Override
    public void init(FilterConfig filterConfig) {
        permissionsCache = Caffeine.newBuilder()
                .maximumSize(10000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .refreshAfterWrite(5, TimeUnit.MINUTES)
                .recordStats()
                .build(this::loadUserPermissions);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
      
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
      
        String userId = getUserIdFromRequest(req);
        String requestedResource = req.getRequestURI();
      
        // 从缓存获取用户权限
        UserPermissions permissions = permissionsCache.get(userId);
      
        if (!permissions.hasAccess(requestedResource)) {
            log.warn("用户 {} 无权访问 {}", userId, requestedResource);
            resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
            return;
        }
      
        chain.doFilter(request, response);
    }

    /**
     * 加载用户权限(缓存未命中时调用)
     */
    private UserPermissions loadUserPermissions(String userId) {
        log.debug("从数据库加载用户 {} 的权限", userId);
        // 从数据库加载权限
        return new UserPermissions(userId);
    }

    private String getUserIdFromRequest(HttpServletRequest request) {
        // 从 Token 或 Session 中获取用户ID
        return "user123";
    }

    /**
     * 用户权限类
     */
    static class UserPermissions {
        private final String userId;
      
        public UserPermissions(String userId) {
            this.userId = userId;
        }
      
        public boolean hasAccess(String resource) {
            // 检查权限逻辑
            return true;
        }
    }
}

二十、完整示例项目

20.1 项目依赖(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>filter-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.12.3</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.12.3</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.12.3</version>
            <scope>runtime</scope>
        </dependency>

        <!-- Caffeine Cache -->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>

        <!-- Guava (布隆过滤器、限流) -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>32.1.3-jre</version>
        </dependency>

        <!-- Micrometer (监控) -->
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-core</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

20.2 应用配置(application.yml)

server:
  port: 8080
  servlet:
    context-path: /api

spring:
  application:
    name: filter-demo

# JWT 配置
jwt:
  secret: your_secret_key_must_be_at_least_256_bits_long_for_hs256_algorithm
  expiration: 3600000  # 1小时(毫秒)

# Filter 配置
app:
  filter:
    # 字符编码
    encoding:
      enabled: true
      charset: UTF-8
  
    # CORS 跨域
    cors:
      enabled: true
      allowed-origins: "*"
      allowed-methods: "GET,POST,PUT,DELETE,OPTIONS"
      allowed-headers: "*"
      max-age: 3600
  
    # XSS 防护
    xss:
      enabled: true
      excluded-paths:
        - /public/**
  
    # 限流
    rate-limit:
      enabled: true
      permits-per-second: 10
      excluded-paths:
        - /health
        - /actuator/**
  
    # 日志
    logging:
      enabled: true
      log-request-body: true
      log-response-body: true
      excluded-paths:
        - /actuator/**
        - /static/**
  
    # 调试
    debug:
      enabled: false  # 生产环境关闭

# 日志配置
logging:
  level:
    root: INFO
    com.example: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

20.3 启动类

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;

/**
 * 应用启动类
 */
@SpringBootApplication
@ServletComponentScan  // 扫描 @WebFilter 等注解
@EnableAsync           // 启用异步支持
public class FilterDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(FilterDemoApplication.class, args);
    }
}

20.4 完整的 Filter 配置类

package com.example.config;

import com.example.filter.*;
import com.example.properties.FilterProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

/**
 * Filter 统一配置
 */
@Configuration
@EnableConfigurationProperties(FilterProperties.class)
@RequiredArgsConstructor
public class FilterConfig {

    private final FilterProperties filterProperties;

    /**
     * 1. 字符编码过滤器(最高优先级)
     */
    @Bean
    public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
        FilterRegistrationBean<CharacterEncodingFilter> bean = new FilterRegistrationBean<>();
      
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        bean.setFilter(filter);
        bean.addUrlPatterns("/*");
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        bean.setName("characterEncodingFilter");
        bean.setEnabled(filterProperties.getEncoding().isEnabled());
      
        return bean;
    }

    /**
     * 2. CORS 跨域过滤器
     */
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>();
      
        CorsFilter filter = new CorsFilter();
        bean.setFilter(filter);
        bean.addUrlPatterns("/*");
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
        bean.setName("corsFilter");
        bean.setEnabled(filterProperties.getCors().isEnabled());
      
        return bean;
    }

    /**
     * 3. XSS 防护过滤器
     */
    @Bean
    public FilterRegistrationBean<XssFilter> xssFilter() {
        FilterRegistrationBean<XssFilter> bean = new FilterRegistrationBean<>();
      
        XssFilter filter = new XssFilter();
        bean.setFilter(filter);
        bean.addUrlPatterns("/*");
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 2);
        bean.setName("xssFilter");
        bean.setEnabled(filterProperties.getXss().isEnabled());
      
        return bean;
    }

    /**
     * 4. JWT 认证过滤器
     */
    @Bean
    public FilterRegistrationBean<JwtAuthenticationFilter> jwtAuthenticationFilter(
            JwtAuthenticationFilter filter) {
        FilterRegistrationBean<JwtAuthenticationFilter> bean = new FilterRegistrationBean<>();
      
        bean.setFilter(filter);
        bean.addUrlPatterns("/api/*");
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 3);
        bean.setName("jwtAuthenticationFilter");
      
        return bean;
    }

    /**
     * 5. 限流过滤器
     */
    @Bean
    public FilterRegistrationBean<RateLimitFilter> rateLimitFilter() {
        FilterRegistrationBean<RateLimitFilter> bean = new FilterRegistrationBean<>();
      
        RateLimitFilter filter = new RateLimitFilter();
        bean.setFilter(filter);
        bean.addUrlPatterns("/*");
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 4);
        bean.setName("rateLimitFilter");
        bean.setEnabled(filterProperties.getRateLimit().isEnabled());
      
        return bean;
    }

    /**
     * 6. 请求日志过滤器
     */
    @Bean
    public FilterRegistrationBean<RequestLoggingFilter> requestLoggingFilter() {
        FilterRegistrationBean<RequestLoggingFilter> bean = new FilterRegistrationBean<>();
      
        RequestLoggingFilter filter = new RequestLoggingFilter();
        bean.setFilter(filter);
        bean.addUrlPatterns("/*");
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 5);
        bean.setName("requestLoggingFilter");
        bean.setEnabled(filterProperties.getLogging().isEnabled());
      
        return bean;
    }

    /**
     * 7. 性能监控过滤器
     */
    @Bean
    public FilterRegistrationBean<MetricsFilter> metricsFilter(MetricsFilter filter) {
        FilterRegistrationBean<MetricsFilter> bean = new FilterRegistrationBean<>();
      
        bean.setFilter(filter);
        bean.addUrlPatterns("/*");
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 6);
        bean.setName("metricsFilter");
      
        return bean;
    }

    /**
     * 8. 调试过滤器(仅开发环境)
     */
    @Bean
    public FilterRegistrationBean<DebugFilter> debugFilter() {
        FilterRegistrationBean<DebugFilter> bean = new FilterRegistrationBean<>();
      
        DebugFilter filter = new DebugFilter();
        bean.setFilter(filter);
        bean.addUrlPatterns("/*");
        bean.setOrder(Ordered.LOWEST_PRECEDENCE);
        bean.setName("debugFilter");
        bean.setEnabled(filterProperties.getDebug().isEnabled());
      
        return bean;
    }
}

20.5 Filter 配置属性类

package com.example.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.ArrayList;
import java.util.List;

/**
 * Filter 配置属性
 */
@Data
@ConfigurationProperties(prefix = "app.filter")
public class FilterProperties {

    private EncodingConfig encoding = new EncodingConfig();
    private CorsConfig cors = new CorsConfig();
    private XssConfig xss = new XssConfig();
    private RateLimitConfig rateLimit = new RateLimitConfig();
    private LoggingConfig logging = new LoggingConfig();
    private DebugConfig debug = new DebugConfig();

    @Data
    public static class EncodingConfig {
        private boolean enabled = true;
        private String charset = "UTF-8";
    }

    @Data
    public static class CorsConfig {
        private boolean enabled = true;
        private String allowedOrigins = "*";
        private String allowedMethods = "GET,POST,PUT,DELETE,OPTIONS";
        private String allowedHeaders = "*";
        private long maxAge = 3600;
    }

    @Data
    public static class XssConfig {
        private boolean enabled = true;
        private List<String> excludedPaths = new ArrayList<>();
    }

    @Data
    public static class RateLimitConfig {
        private boolean enabled = true;
        private double permitsPerSecond = 10.0;
        private List<String> excludedPaths = new ArrayList<>();
    }

    @Data
    public static class LoggingConfig {
        private boolean enabled = true;
        private boolean logRequestBody = true;
        private boolean logResponseBody = true;
        private List<String> excludedPaths = new ArrayList<>();
    }

    @Data
    public static class DebugConfig {
        private boolean enabled = false;
    }
}

20.6 测试控制器

package com.example.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 测试控制器
 */
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

    /**
     * 测试基本请求
     */
    @GetMapping("/hello")
    public Map<String, Object> hello(HttpServletRequest request) {
        Map<String, Object> result = new HashMap<>();
        result.put("message", "Hello, Filter!");
        result.put("requestId", request.getAttribute("requestId"));
        result.put("timestamp", System.currentTimeMillis());
        return result;
    }

    /**
     * 测试 POST 请求
     */
    @PostMapping("/echo")
    public Map<String, Object> echo(@RequestBody Map<String, Object> body) {
        log.info("收到请求体: {}", body);
        return body;
    }

    /**
     * 测试需要认证的接口
     */
    @GetMapping("/secure")
    public Map<String, Object> secure() {
        Map<String, Object> result = new HashMap<>();
        result.put("message", "This is a secure endpoint");
        result.put("timestamp", System.currentTimeMillis());
        return result;
    }

    /**
     * 测试限流
     */
    @GetMapping("/rate-limit-test")
    public Map<String, Object> rateLimitTest() {
        Map<String, Object> result = new HashMap<>();
        result.put("message", "Rate limit test");
        result.put("timestamp", System.currentTimeMillis());
        return result;
    }

    /**
     * 测试异常处理
     */
    @GetMapping("/error")
    public Map<String, Object> error() {
        throw new RuntimeException("测试异常");
    }

    /**
     * 公共接口(不需要认证)
     */
    @GetMapping("/public/info")
    public Map<String, Object> publicInfo() {
        Map<String, Object> result = new HashMap<>();
        result.put("message", "Public information");
        result.put("version", "1.0.0");
        return result;
    }
}

20.7 全局异常处理

package com.example.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 全局异常处理器
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理所有异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, Object>> handleException(
            Exception e, HttpServletRequest request) {
      
        log.error("请求异常: {} {}", request.getMethod(), request.getRequestURI(), e);
      
        Map<String, Object> error = new HashMap<>();
        error.put("error", "Internal Server Error");
        error.put("message", e.getMessage());
        error.put("path", request.getRequestURI());
        error.put("timestamp", System.currentTimeMillis());
      
        String requestId = (String) request.getAttribute("requestId");
        if (requestId != null) {
            error.put("requestId", requestId);
        }
      
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(error);
    }

    /**
     * 处理运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Map<String, Object>> handleRuntimeException(
            RuntimeException e, HttpServletRequest request) {
      
        log.error("运行时异常: {} {}", request.getMethod(), request.getRequestURI(), e);
      
        Map<String, Object> error = new HashMap<>();
        error.put("error", "Runtime Error");
        error.put("message", e.getMessage());
        error.put("path", request.getRequestURI());
        error.put("timestamp", System.currentTimeMillis());
      
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(error);
    }
}

二十一、测试用例

21.1 Filter 单元测试

package com.example.filter;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

/**
 * Filter 单元测试
 */
class FilterUnitTest {

    @Mock
    private HttpServletRequest request;

    @Mock
    private HttpServletResponse response;

    @Mock
    private FilterChain filterChain;

    private JwtAuthenticationFilter jwtFilter;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        jwtFilter = new JwtAuthenticationFilter();
    }

    @Test
    void testDoFilter_WithValidToken() throws ServletException, IOException {
        // Given
        when(request.getHeader("Authorization")).thenReturn("Bearer valid-token");
        when(request.getRequestURI()).thenReturn("/api/test");

        // When
        jwtFilter.doFilter(request, response, filterChain);

        // Then
        verify(filterChain).doFilter(request, response);
        verify(response, never()).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    }

    @Test
    void testDoFilter_WithoutToken() throws ServletException, IOException {
        // Given
        when(request.getHeader("Authorization")).thenReturn(null);
        when(request.getRequestURI()).thenReturn("/api/test");
      
        StringWriter stringWriter = new StringWriter();
        PrintWriter writer = new PrintWriter(stringWriter);
        when(response.getWriter()).thenReturn(writer);

        // When
        jwtFilter.doFilter(request, response, filterChain);

        // Then
        verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        verify(filterChain, never()).doFilter(request, response);
    }

    @Test
    void testDoFilter_PublicEndpoint() throws ServletException, IOException {
        // Given
        when(request.getRequestURI()).thenReturn("/public/info");

        // When
        jwtFilter.doFilter(request, response, filterChain);

        // Then
        verify(filterChain).doFilter(request, response);
        verify(response, never()).setStatus(anyInt());
    }

    @Test
    void testShouldNotFilter() {
        // Given
        when(request.getRequestURI()).thenReturn("/public/test");

        // When
        boolean result = jwtFilter.shouldNotFilter(request);

        // Then
        assertTrue(result);
    }

    @Test
    void testShouldFilter() {
        // Given
        when(request.getRequestURI()).thenReturn("/api/secure");

        // When
        boolean result = jwtFilter.shouldNotFilter(request);

        // Then
        assertFalse(result);
    }
}

21.2 Filter 集成测试

package com.example.filter;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Filter 集成测试
 */
@SpringBootTest
@AutoConfigureMockMvc
class FilterIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testFilterChain_WithValidToken() throws Exception {
        mockMvc.perform(get("/api/test/hello")
                        .header("Authorization", "Bearer valid-token")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.message").value("Hello, Filter!"));
    }

    @Test
    void testFilterChain_WithoutToken() throws Exception {
        mockMvc.perform(get("/api/test/secure")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isUnauthorized());
    }

    @Test
    void testFilterChain_PublicEndpoint() throws Exception {
        mockMvc.perform(get("/api/test/public/info")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.message").value("Public information"));
    }

    @Test
    void testCorsFilter() throws Exception {
        mockMvc.perform(options("/api/test/hello")
                        .header("Origin", "http://localhost:3000")
                        .header("Access-Control-Request-Method", "GET"))
                .andExpect(status().isOk())
                .andExpect(header().exists("Access-Control-Allow-Origin"))
                .andExpect(header().string("Access-Control-Allow-Origin", "*"));
    }

    @Test
    void testRateLimitFilter() throws Exception {
        // 连续发送多个请求测试限流
        for (int i = 0; i < 15; i++) {
            mockMvc.perform(get("/api/test/rate-limit-test")
                            .contentType(MediaType.APPLICATION_JSON))
                    .andExpect(status().is(anyOf(200, 429)));
        }
    }

    @Test
    void testXssFilter() throws Exception {
        String xssPayload = "{\"name\":\"<script>alert('XSS')</script>\"}";
      
        mockMvc.perform(post("/api/test/echo")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(xssPayload))
                .andExpect(status().isOk());
    }

    @Test
    void testCharacterEncoding() throws Exception {
        String chineseText = "{\"message\":\"你好,世界\"}";
      
        mockMvc.perform(post("/api/test/echo")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(chineseText))
                .andExpect(status().isOk())
                .andExpect(content().encoding("UTF-8"));
    }
}

二十二、部署与运维

22.1 生产环境配置

# application-prod.yml
server:
  port: 8080
  tomcat:
    threads:
      max: 200
      min-spare: 10
    max-connections: 10000
    accept-count: 100

spring:
  application:
    name: filter-demo

# Filter 生产配置
app:
  filter:
    encoding:
      enabled: true
      charset: UTF-8
  
    cors:
      enabled: true
      allowed-origins: "https://yourdomain.com"  # 指定域名
      allowed-methods: "GET,POST,PUT,DELETE"
      allowed-headers: "Content-Type,Authorization"
      max-age: 3600
  
    xss:
      enabled: true
      excluded-paths:
        - /public/**
        - /actuator/**
  
    rate-limit:
      enabled: true
      permits-per-second: 100  # 生产环境提高限流阈值
      excluded-paths:
        - /actuator/health
  
    logging:
      enabled: true
      log-request-body: false   # 生产环境关闭请求体日志
      log-response-body: false  # 生产环境关闭响应体日志
      excluded-paths:
        - /actuator/**
        - /static/**
  
    debug:
      enabled: false  # 生产环境关闭调试

# JWT 配置
jwt:
  secret: ${JWT_SECRET:your_production_secret_key}
  expiration: 7200000  # 2小时

# 日志配置
logging:
  level:
    root: WARN
    com.example: INFO
  file:
    name: /var/log/filter-demo/application.log
    max-size: 100MB
    max-history: 30

22.2 Docker 部署

# Dockerfile
FROM openjdk:17-jdk-slim

WORKDIR /app

# 复制 jar 文件
COPY target/filter-demo-1.0.0.jar app.jar

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

# 启动应用
ENTRYPOINT ["java", \
    "-Xms512m", \
    "-Xmx1024m", \
    "-XX:+UseG1GC", \
    "-XX:MaxGCPauseMillis=200", \
    "-Dspring.profiles.active=prod", \
    "-jar", \
    "app.jar"]
# docker-compose.yml
version: '3.8'

services:
  filter-demo:
    build: .
    image: filter-demo:1.0.0
    container_name: filter-demo
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - JWT_SECRET=${JWT_SECRET}
    volumes:
      - ./logs:/var/log/filter-demo
    restart: unless-stopped
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

22.3 监控与告警

package com.example.monitoring;

import io.micrometer.core.instrument.MeterRegistry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * Filter 监控指标收集
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class FilterMonitoring {

    private final MeterRegistry meterRegistry;

    /**
     * 定期输出 Filter 统计信息
     */
    @Scheduled(fixedRate = 60000)  // 每分钟
    public void reportMetrics() {
        // 请求总数
        double totalRequests = meterRegistry.counter("http.requests.total").count();
      
        // 平均响应时间
        double avgResponseTime = meterRegistry.timer("http.server.requests")
                .mean(java.util.concurrent.TimeUnit.MILLISECONDS);
      
        // 错误率
        double errorCount = meterRegistry.counter("http.requests.total", 
                "status", "5xx").count();
        double errorRate = totalRequests > 0 ? (errorCount / totalRequests) * 100 : 0;
      
        log.info("Filter 监控指标 - 总请求数: {}, 平均响应时间: {}ms, 错误率: {}%",
                totalRequests, avgResponseTime, String.format("%.2f", errorRate));
    }
}

二十三、总结与最佳实践

23.1 核心知识点总结

  1. Filter 生命周期

    • init()doFilter()destroy()
    • 每个请求都会调用 doFilter()
  2. Filter 注册方式

    • @WebFilter + @ServletComponentScan
    • @Component + @Order
    • FilterRegistrationBean(推荐)
  3. Filter 执行顺序

    • 使用 @OrdersetOrder() 指定
    • 数字越小,优先级越高
  4. Filter 放行机制

    • 必须调用 chain.doFilter() 才能继续
    • 不调用则中断请求链

23.2 开发规范清单

必须遵守的规范:

  • 继承 OncePerRequestFilter 避免重复执行
  • 实现 shouldNotFilter() 排除不需要过滤的路径
  • 妥善处理异常,使用 try-catch-finally
  • 记录详细的日志便于调试
  • 使用 @Order 明确指定执行顺序

推荐的做法:

  • 使用 FilterRegistrationBean 进行配置
  • 单一职责,每个 Filter 只做一件事
  • 使用缓存减少重复计算
  • 考虑使用异步处理非关键操作
  • 添加详细的注释和文档

应该避免的错误:

  • 忘记调用 chain.doFilter()
  • 同时使用多种注册方式
  • 在 Filter 中执行耗时操作
  • 未处理异常导致请求失败
  • 多次读取请求体而不使用 Wrapper

23.3 性能优化建议

  1. 使用缓存

    • 缓存验证结果(JWT、权限等)
    • 使用 Caffeine 或 Redis
  2. 异步处理

    • 日志记录异步化
    • 非关键操作异步执行
  3. 减少对象创建

    • 使用对象池
    • 重用对象
  4. 优化算法

    • 使用布隆过滤器检查黑名单
    • 使用令牌桶算法限流

23.4 安全建议

  1. 输入验证

    • XSS 防护
    • SQL 注入防护
    • 参数验证
  2. 认证授权

    • JWT Token 验证
    • API Key 验证
    • 权限检查
  3. 限流防护

    • IP 限流
    • 用户限流
    • 接口限流
  4. 日志审计

    • 记录所有请求
    • 记录敏感操作
    • 保留审计日志

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇