概念:Filter过滤器,是JavaWeb三大组件(Servlet、Filter、Listener)之一。
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

一、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 防护
- ✅ 请求参数验证
- ✅ 性能监控


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

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 三者对比
| 特性 | Filter | Interceptor | AOP |
|---|---|---|---|
| 规范 | 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 设计原则
单一职责原则
- 每个 Filter 只负责一个功能
- 避免在一个 Filter 中处理多个不相关的逻辑
最小化原则
- 只在必要的路径上应用 Filter
- 使用
shouldNotFilter()排除不需要过滤的请求
性能优先原则
- 避免在 Filter 中执行耗时操作
- 使用缓存减少重复计算
- 考虑异步处理日志等非关键操作
异常处理原则
- 必须妥善处理异常,避免影响请求链
- 记录详细的错误日志
- 提供友好的错误响应
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 核心要点
生命周期
init()– 初始化(服务器启动时)doFilter()– 过滤处理(每次请求)destroy()– 销毁(服务器关闭时)
执行顺序
- 使用
@Order、FilterRegistrationBean.setOrder()或实现Ordered接口 - 数字越小,优先级越高
- 使用
放行机制
- 必须调用
chain.doFilter(request, response)才能继续执行 - 不调用则中断请求链
- 必须调用
适用场景
- 字符编码、跨域、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 │
│ ↓ │
│ 响应返回 │
│ │
└─────────────────────────────────────────────────────────┘
选择建议:
使用 Filter 的场景:
- 需要在 Servlet 容器级别处理
- 不依赖 Spring 容器
- 处理所有类型的请求(包括静态资源)
- 字符编码、跨域、XSS 防护等
使用 Interceptor 的场景:
- 需要访问 Spring Bean
- 需要访问 Controller 的信息
- 只处理 Spring MVC 的请求
- 登录验证、权限检查等
使用 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 核心知识点总结
Filter 生命周期
init()→doFilter()→destroy()- 每个请求都会调用
doFilter()
Filter 注册方式
@WebFilter+@ServletComponentScan@Component+@OrderFilterRegistrationBean(推荐)
Filter 执行顺序
- 使用
@Order或setOrder()指定 - 数字越小,优先级越高
- 使用
Filter 放行机制
- 必须调用
chain.doFilter()才能继续 - 不调用则中断请求链
- 必须调用
23.2 开发规范清单
✅ 必须遵守的规范:
- 继承
OncePerRequestFilter避免重复执行 - 实现
shouldNotFilter()排除不需要过滤的路径 - 妥善处理异常,使用 try-catch-finally
- 记录详细的日志便于调试
- 使用
@Order明确指定执行顺序
✅ 推荐的做法:
- 使用
FilterRegistrationBean进行配置 - 单一职责,每个 Filter 只做一件事
- 使用缓存减少重复计算
- 考虑使用异步处理非关键操作
- 添加详细的注释和文档
❌ 应该避免的错误:
- 忘记调用
chain.doFilter() - 同时使用多种注册方式
- 在 Filter 中执行耗时操作
- 未处理异常导致请求失败
- 多次读取请求体而不使用 Wrapper
23.3 性能优化建议
使用缓存
- 缓存验证结果(JWT、权限等)
- 使用 Caffeine 或 Redis
异步处理
- 日志记录异步化
- 非关键操作异步执行
减少对象创建
- 使用对象池
- 重用对象
优化算法
- 使用布隆过滤器检查黑名单
- 使用令牌桶算法限流
23.4 安全建议
输入验证
- XSS 防护
- SQL 注入防护
- 参数验证
认证授权
- JWT Token 验证
- API Key 验证
- 权限检查
限流防护
- IP 限流
- 用户限流
- 接口限流
日志审计
- 记录所有请求
- 记录敏感操作
- 保留审计日志





