一、为什么需要过滤器
在Java后端开发中,过滤器(Filter)是处理请求/响应的核心组件,主要用于统一拦截请求、做预处理(如登录校验、参数过滤、日志记录)和后处理(如响应数据加密、跨域配置)。
而实际开发中,单一过滤器无法满足多场景需求(例如:同时需要登录校验、接口限流、请求日志打印、参数脱敏),此时就需要配置多过滤器,并严格控制其执行顺序------这也是SpringBoot多过滤器的核心价值所在。
======= 🌟 青柠来相伴,代码更简单。🌟 =======
📚 本文所有内容,我都整理在了 青柠合集 里。👇
🎯 搜索关注【青柠代码录】,即可查看所有合集文章 ~
======= 🌟 ================ 🌟 =======
二、过滤器核心认知
2.1 过滤器本质与作用
过滤器是Java Servlet规范中的组件,运行在Servlet之前、Controller之前,属于请求处理的"前置拦截器",核心作用:
- 请求拦截:拦截指定URL的请求,拒绝非法请求(如未登录、权限不足);
- 请求预处理:对请求参数进行校验、转义、脱敏(如手机号、身份证号隐藏),设置请求头(如跨域、Token传递);
- 响应后处理:对响应数据进行加密、格式化,添加响应头;
- 日志记录:记录请求URL、请求参数、响应状态、执行耗时等,用于排查问题。
2.2 SpringBoot中过滤器的核心特性
SpringBoot对Servlet过滤器进行了封装,无需像传统SSM那样在web.xml中配置,而是通过注解或Java配置类即可快速实现,核心特性:
- 无XML配置:通过@WebFilter、@Configuration等注解简化配置;
- 支持多过滤器配置:可同时注册多个过滤器,通过Order注解或FilterRegistrationBean控制执行顺序;
- 支持URL匹配:可精准指定过滤器拦截的URLPattern(如/*、/api/*、/admin/*);
- 支持排除URL:可配置不拦截的URL(如登录接口、静态资源),贴合场景。
三、过滤器的3种配置方式
SpringBoot配置多过滤器,核心是「注册过滤器」+「控制执行顺序」,以下3种方式均贴合实战,优先推荐方式2和方式3(灵活度更高)。
3.1 方式1:@WebFilter + @ServletComponentScan(简单快捷,适合小型项目)
核心逻辑:通过@WebFilter注解标记过滤器,指定拦截URL;通过@Order注解指定执行顺序(值越小,执行越早);在启动类添加@ServletComponentScan,扫描过滤器所在包。
3.1.1 步骤1:创建第一个过滤器(登录校验过滤器)
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.core.annotation.Order;
import java.io.IOException;
/**
* 登录校验过滤器(优先级1,最先执行)
* 拦截所有请求,排除登录、注册接口,未登录则返回401
*/
@WebFilter(urlPatterns = "/*", filterName = "loginFilter") // urlPatterns:拦截所有请求;filterName:过滤器名称(唯一)
@Order(1) // 执行顺序:1(值越小,执行越早)
public class LoginFilter implements Filter {
// 排除的URL(无需登录即可访问)
private static final String[] EXCLUDE_URLS = {"/api/user/login", "/api/user/register", "/static/*"};
/**
* 过滤器初始化:项目启动时执行一次,用于初始化资源(如加载黑名单、初始化缓存)
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
System.out.println("LoginFilter 初始化完成");
}
/**
* 核心拦截方法:每次请求都会执行,处理请求/响应
* @param servletRequest 请求对象
* @param servletResponse 响应对象
* @param filterChain 过滤器链:用于放行请求到下一个过滤器或Servlet
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 转换为HttpServletRequest(获取请求相关信息,如URL、Session)
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestUrl = request.getRequestURI(); // 获取请求URL
// 1. 判断是否为排除的URL,若是则直接放行
boolean isExclude = false;
for (String excludeUrl : EXCLUDE_URLS) {
if (requestUrl.startsWith(excludeUrl)) {
isExclude = true;
break;
}
}
if (isExclude) {
filterChain.doFilter(request, response); // 放行:进入下一个过滤器或Controller
return;
}
// 2. 校验登录状态(从Session中获取用户信息)
HttpSession session = request.getSession();
Object user = session.getAttribute("loginUser");
if (user == null) {
// 未登录:返回401状态码,提示未登录
response.setStatus(401);
response.getWriter().write("请先登录");
return;
}
// 3. 登录校验通过,放行到下一个过滤器
filterChain.doFilter(request, response);
}
/**
* 过滤器销毁:项目停止时执行,用于释放资源(如关闭连接、清理缓存)
*/
@Override
public void destroy() {
Filter.super.destroy();
System.out.println("LoginFilter 销毁完成");
}
}
3.1.2 步骤2:创建第二个过滤器(请求日志过滤器)
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.annotation.Order;
import java.io.IOException;
import java.util.Date;
/**
* 请求日志过滤器(优先级2,在LoginFilter之后执行)
* 记录请求URL、请求方法、执行耗时、客户端IP等信息
*/
@WebFilter(urlPatterns = "/api/*", filterName = "logFilter") // 只拦截/api/*开头的接口请求
@Order(2) // 执行顺序:2(在LoginFilter之后)
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
System.out.println("LogFilter 初始化完成");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
// 1. 记录请求开始时间
long startTime = System.currentTimeMillis();
// 2. 获取请求核心信息
String requestUrl = request.getRequestURI();
String requestMethod = request.getMethod(); // GET/POST/PUT/DELETE
String clientIp = request.getRemoteAddr(); // 客户端IP
// 3. 放行请求,执行后续过滤器/Controller
filterChain.doFilter(request, servletResponse);
// 4. 记录请求结束时间,计算耗时
long endTime = System.currentTimeMillis();
long costTime = endTime - startTime;
// 打印日志(实际开发中建议使用SLF4J+Logback,此处简化为System.out)
System.out.printf("请求日志:URL=%s,方法=%s,客户端IP=%s,耗时=%dms%n",
requestUrl, requestMethod, clientIp, costTime);
}
@Override
public void destroy() {
Filter.super.destroy();
System.out.println("LogFilter 销毁完成");
}
}
3.1.3 步骤3:启动类添加@ServletComponentScan
必须在SpringBoot启动类添加@ServletComponentScan,否则@WebFilter注解标记的过滤器无法被扫描到,导致不生效。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan(basePackages = "com.qng.code.filter") // 扫描过滤器所在包(替换为自己的包路径)
public class SpringBootFilterDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootFilterDemoApplication.class, args);
}
}
3.1.4 方式1注意事项(避坑)
- @Order注解的优先级:值越小,执行越早;若不指定@Order,过滤器执行顺序由类名首字母排序(不确定,不推荐);
- urlPatterns配置:/* 表示拦截所有请求,/api/* 表示拦截/api开头的请求,可配置多个URL(如urlPatterns = {"/api/ ", "/admin/"});
- 排除URL:需自己手动判断(如LoginFilter中的EXCLUDE_URLS),方式1不支持直接配置excludeUrlPatterns,需手动编码实现。
3.2 方式2:FilterRegistrationBean(推荐,灵活度高,支持排除URL)
核心逻辑:通过Java配置类,创建FilterRegistrationBean对象,将过滤器注册到Spring容器中,可直接配置拦截URL、排除URL、执行顺序,无需@WebFilter和@ServletComponentScan,是开发中最常用的方式。
3.2.1 步骤1:创建过滤器(无需@WebFilter和@Order注解)
复用3.1.1和3.1.2中的LoginFilter和LogFilter,删除类上的@WebFilter和@Order注解(其余代码不变)。
3.2.2 步骤2:创建配置类,注册多过滤器
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 多过滤器配置类(推荐)
* 通过FilterRegistrationBean注册过滤器,灵活配置顺序、拦截URL、排除URL
*/
@Configuration
public class FilterConfig {
/**
* 注册登录校验过滤器
* @return FilterRegistrationBean<LoginFilter>
*/
@Bean
public FilterRegistrationBean<LoginFilter> loginFilterRegistration() {
FilterRegistrationBean<LoginFilter> registrationBean = new FilterRegistrationBean<>();
// 1. 设置过滤器实例
registrationBean.setFilter(new LoginFilter());
// 2. 设置拦截URL(可配置多个)
registrationBean.addUrlPatterns("/*");
// 3. 设置排除URL(常用,无需手动编码判断)
registrationBean.addInitParameter("excludedUrls", "/api/user/login,/api/user/register,/static/*");
// 4. 设置执行顺序(值越小,执行越早)
registrationBean.setOrder(1);
// 5. 设置过滤器名称(唯一)
registrationBean.setName("loginFilter");
return registrationBean;
}
/**
* 注册请求日志过滤器
* @return FilterRegistrationBean<LogFilter>
*/
@Bean
public FilterRegistrationBean<LogFilter> logFilterRegistration() {
FilterRegistrationBean<LogFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new LogFilter());
// 拦截/api/*开头的接口
registrationBean.addUrlPatterns("/api/*");
// 执行顺序:2(在LoginFilter之后)
registrationBean.setOrder(2);
registrationBean.setName("logFilter");
return registrationBean;
}
// 可继续添加更多过滤器(如参数脱敏过滤器、限流过滤器)
@Bean
public FilterRegistrationBean<DesensitizationFilter> desensitizationFilterRegistration() {
FilterRegistrationBean<DesensitizationFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new DesensitizationFilter());
registrationBean.addUrlPatterns("/api/*");
registrationBean.setOrder(3); // 在LogFilter之后执行
registrationBean.setName("desensitizationFilter");
return registrationBean;
}
}
3.2.3 步骤3:优化过滤器,获取排除URL(可选)
方式2中通过addInitParameter配置了排除URL,可在过滤器中通过FilterConfig获取,无需手动定义EXCLUDE_URLS,更灵活:
// 优化LoginFilter的init和doFilter方法
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
// 获取配置的排除URL,分割为数组
String excludedUrls = filterConfig.getInitParameter("excludedUrls");
if (excludedUrls != null && !excludedUrls.isEmpty()) {
EXCLUDE_URLS = excludedUrls.split(",");
}
System.out.println("LoginFilter 初始化完成");
}
// doFilter方法中的排除URL判断逻辑不变
3.2.4 方式2核心优势(推荐原因)
- 支持直接配置excludeUrlPatterns(或通过initParameter配置),无需手动编码;
- 执行顺序由setOrder()控制,更直观、更灵活;
- 可通过registrationBean.setEnabled(false)动态禁用某个过滤器(适合多环境切换,如测试环境禁用登录校验);
- 过滤器与配置分离,便于维护(如修改拦截URL,无需修改过滤器类)。
3.3 方式3:@Component + @Order(简化配置,适合无URL匹配需求)
核心逻辑:给过滤器类添加@Component注解,让Spring自动扫描注册;通过@Order注解控制执行顺序,无需配置URLPatterns(默认拦截所有请求),适合无需精准URL匹配的场景(如全局日志、全局跨域)。
3.3.1 示例:全局跨域过滤器(无URL匹配,全局生效)
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 全局跨域过滤器(优先级0,最先执行)
* 无需配置URL,全局拦截所有请求,解决跨域问题
*/
@Component // 让Spring自动扫描注册
@Order(0) // 执行顺序:0(比LoginFilter更早)
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 设置跨域允许的来源(*表示允许所有来源,生产环境建议指定具体域名)
response.setHeader("Access-Control-Allow-Origin", "*");
// 设置允许的请求方法(GET/POST/PUT/DELETE等)
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
// 设置允许的请求头
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Token");
// 设置跨域请求的有效期(秒)
response.setHeader("Access-Control-Max-Age", "3600");
// 放行请求
filterChain.doFilter(servletRequest, response);
}
}
3.3.2 方式3注意事项
- @Component注解会让过滤器自动注册,默认拦截所有请求(/*),无法配置URLPatterns和排除URL;
- 适合全局生效的过滤器(如跨域、全局日志),不适合需要精准URL拦截的场景(如登录校验、接口限流);
- 执行顺序由@Order注解控制,与方式1一致。
四、多过滤器执行顺序
4.1 执行顺序的核心规则
SpringBoot多过滤器的执行顺序,本质是「过滤器链(FilterChain)的顺序」,核心规则:
- 优先级数值越小,执行越早(拦截请求时);响应后处理时,顺序相反(优先级大的先执行);
- 同优先级的过滤器,执行顺序由注册顺序决定(方式2中,配置类中@Bean的顺序;方式1中,类名首字母排序);
- 过滤器链的执行流程:请求 → 过滤器1 → 过滤器2 → ... → Controller → 过滤器2 → 过滤器1 → 响应。
4.2 执行顺序实战演示(结合方式2)
假设配置3个过滤器,顺序:CorsFilter(Order=0)→ LoginFilter(Order=1)→ LogFilter(Order=2),执行流程如下:
1. 客户端发送请求(如/api/user/getInfo);
2. 先执行CorsFilter(Order=0):设置跨域响应头,放行;
3. 再执行LoginFilter(Order=1):校验登录状态,登录通过,放行;
4. 再执行LogFilter(Order=2):记录请求开始时间,放行;
5. 请求到达Controller,执行业务逻辑,返回响应;
6. 响应返回,先执行LogFilter(后处理):记录请求结束时间、耗时;
7. 再执行LoginFilter(后处理,可选):如清理临时资源;
8. 再执行CorsFilter(后处理,可选):如添加响应附加信息;
9. 响应返回给客户端。
4.3 常见坑点:顺序配置错误导致的问题
- 坑点1:LoginFilter顺序晚于LogFilter → 未登录请求也会被记录日志,造成日志冗余;
- 坑点2:CorsFilter顺序晚于其他过滤器 → 跨域请求被LoginFilter拦截,返回401,前端无法获取跨域响应头,导致跨域失败;
- 坑点3:同优先级过滤器,注册顺序错误 → 如限流过滤器晚于业务过滤器,无法有效拦截非法请求。
五、实战:多过滤器综合案例
结合实际开发场景,配置4个过滤器,覆盖「跨域→登录校验→接口限流→请求日志」,完整实现多过滤器的协同工作。
5.1 案例需求
- CorsFilter:全局跨域,最先执行(Order=0);
- LoginFilter:登录校验,拦截所有请求,排除登录、注册接口(Order=1);
- RateLimitFilter:接口限流,拦截/api/*接口,限制每个IP每分钟最多请求60次(Order=2);
- LogFilter:请求日志,记录/api/*接口的请求信息和耗时(Order=3)。
5.2 代码实现
5.2.1 限流过滤器(RateLimitFilter)
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 接口限流过滤器(Order=2)
* 限制每个IP每分钟最多请求60次,超过则返回429(请求过于频繁)
*/
public class RateLimitFilter implements Filter {
// 存储IP的请求次数:key=IP,value=请求次数
private final Map<String, Integer> ipRequestCount = new HashMap<>();
// 限流阈值:每分钟最多60次
private static final int LIMIT_COUNT = 60;
// 限流时间窗口:60秒(1分钟)
private static final long LIMIT_TIME = 60 * 1000;
// 定时清理过期的IP请求记录(每1分钟清理一次)
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
new Thread(() -> {
while (true) {
try {
// 每隔1分钟,清空所有IP的请求次数
ipRequestCount.clear();
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String clientIp = request.getRemoteAddr();
// 1. 统计IP的请求次数
ipRequestCount.put(clientIp, ipRequestCount.getOrDefault(clientIp, 0) + 1);
int currentCount = ipRequestCount.get(clientIp);
// 2. 判断是否超过限流阈值
if (currentCount > LIMIT_COUNT) {
response.setStatus(429);
response.getWriter().write("请求过于频繁,请1分钟后再试");
return;
}
// 3. 限流通过,放行到下一个过滤器
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
5.2.2 配置类(注册4个过滤器)
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EnterpriseFilterConfig {
// 1. 跨域过滤器(Order=0)
@Bean
public FilterRegistrationBean<CorsFilter> corsFilterRegistration() {
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CorsFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(0);
registrationBean.setName("corsFilter");
return registrationBean;
}
// 2. 登录校验过滤器(Order=1)
@Bean
public FilterRegistrationBean<LoginFilter> loginFilterRegistration() {
FilterRegistrationBean<LoginFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new LoginFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.addInitParameter("excludedUrls", "/api/user/login,/api/user/register");
registrationBean.setOrder(1);
registrationBean.setName("loginFilter");
return registrationBean;
}
// 3. 接口限流过滤器(Order=2)
@Bean
public FilterRegistrationBean<RateLimitFilter> rateLimitFilterRegistration() {
FilterRegistrationBean<RateLimitFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new RateLimitFilter());
registrationBean.addUrlPatterns("/api/*");
registrationBean.setOrder(2);
registrationBean.setName("rateLimitFilter");
return registrationBean;
}
// 4. 请求日志过滤器(Order=3)
@Bean
public FilterRegistrationBean<LogFilter> logFilterRegistration() {
FilterRegistrationBean<LogFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new LogFilter());
registrationBean.addUrlPatterns("/api/*");
registrationBean.setOrder(3);
registrationBean.setName("logFilter");
return registrationBean;
}
}
5.2.3 测试验证
- 未登录访问/api/user/getInfo → LoginFilter拦截,返回401;
- 登录后,1分钟内连续请求/api/user/getInfo 61次 → 第61次被RateLimitFilter拦截,返回429;
- 跨域请求(如前端localhost:8080访问后端localhost:8081)→ CorsFilter生效,无跨域报错;
- 所有/api/*接口请求,都会被LogFilter记录日志,包含URL、方法、IP、耗时。
六、常见问题
6.1 过滤器不生效的4种原因
| 问题原因 | 解决方案 |
|---|---|
| 过滤器未被Spring扫描到(方式1未加@ServletComponentScan,方式2未注册FilterRegistrationBean) | 方式1:启动类添加@ServletComponentScan,指定过滤器包路径;方式2:确保配置类被Spring扫描,FilterRegistrationBean正确注册 |
| URLPatterns配置错误(如需要拦截/api/*,却配置成了/api) | 修正URLPatterns,/*表示所有请求,/api/表示/api开头的所有请求,注意末尾的 |
| 过滤器被禁用(方式2中setEnabled(false)) | 删除setEnabled(false),或改为setEnabled(true),根据环境需求配置 |
| 过滤器顺序错误,被其他过滤器拦截(如CorsFilter晚于LoginFilter) | 调整@Order或setOrder()的值,确保核心过滤器(如CorsFilter、LoginFilter)优先执行 |
6.2 过滤器执行顺序错乱
- 统一使用FilterRegistrationBean配置,通过setOrder()明确指定顺序,避免使用@Order注解(减少歧义);
- 给每个过滤器的Order值留间隔(如0、10、20、30),便于后续插入新的过滤器(如需要在LoginFilter和RateLimitFilter之间添加过滤器,可设置Order=15);
- 通过日志打印过滤器的执行顺序,排查顺序问题(如在每个过滤器的doFilter方法开头打印"当前执行过滤器:XXX")。
6.3 过滤器与拦截器的区别
很多开发者会混淆过滤器和拦截器,此处补充核心区别,避免开发中用错场景:
| 对比维度 | 过滤器(Filter) | 拦截器(Interceptor) |
|---|---|---|
| 所属规范 | Java Servlet规范,属于Servlet层面 | Spring框架规范,属于Spring层面 |
| 执行时机 | Servlet之前、Controller之前 | Controller之前、Controller之后、视图渲染之后 |
| 拦截范围 | 所有请求(包括静态资源、HTML、CSS等) | 只拦截Spring MVC的请求(即Controller的请求) |
| 依赖容器 | 依赖Servlet容器(如Tomcat) | 不依赖Servlet容器,由Spring管理 |
| 适用场景 | 全局跨域、登录校验、请求日志、参数过滤(全局生效) | 接口权限校验、业务逻辑拦截(如接口参数校验、事务控制) |
本文由mdnice多平台发布