文章目录
前言
作为Java开发者,掌握SpringBoot过滤器(Filter)是构建健壮Web应用的基础。本文将系统性地介绍过滤器的所有知识点,从基础概念到高级应用,通过日常化案例帮助你彻底理解并掌握这一重要技术。
一、过滤器基础概念
1.1 什么是过滤器?
过滤器(Filter)是Java Web中的一种组件,它可以在请求到达Servlet之前或响应返回客户端之前对HTTP请求和响应进行预处理和后处理。
通俗理解:想象过滤器就像超市的安检门,所有进入(请求)和离开(响应)超市(你的应用)的人(数据)都要经过它的检查和处理。
1.2 过滤器与拦截器的区别
特性 | 过滤器(Filter) | 拦截器(Interceptor) |
---|---|---|
所属规范 | Java Servlet规范 | Spring框架 |
执行位置 | Servlet容器层面 | Spring MVC层面 |
依赖 | 不依赖任何框架 | 依赖Spring框架 |
获取上下文 | 只能获取Servlet API | 可以获取Spring上下文 |
使用场景 | 字符编码、跨域处理、安全过滤等基础处理 | 权限校验、日志记录、参数处理等业务逻辑 |
1.3 过滤器核心接口
public interface Filter {
// 初始化方法,容器启动时调用
default void init(FilterConfig filterConfig) throws ServletException {}
// 过滤处理方法,每次请求都会调用
void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
// 销毁方法,容器关闭时调用
default void destroy() {}
}
二、SpringBoot中过滤器的基本使用
2.1 创建过滤器的三种方式
方式1:使用@Component注解(最简单)
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@Component
@WebFilter(urlPatterns = "/*") // 过滤所有请求
public class SimpleFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("【简单过滤器】请求进入");
long startTime = System.currentTimeMillis();
// 继续执行过滤器链
chain.doFilter(request, response);
System.out.println("【简单过滤器】响应返回,耗时:"
+ (System.currentTimeMillis() - startTime) + "ms");
}
}
方式2:使用FilterRegistrationBean(更灵活)
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<TimeCostFilter> timeCostFilter() {
FilterRegistrationBean<TimeCostFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new TimeCostFilter());
registration.addUrlPatterns("/*");
registration.setOrder(1); // 设置过滤器顺序
registration.setName("timeCostFilter");
return registration;
}
}
// 自定义过滤器
public class TimeCostFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
long start = System.currentTimeMillis();
chain.doFilter(request, response);
System.out.println("请求处理耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
方式3:实现Filter接口并手动注册(最原始)
public class AuthFilter implements Filter {
// 实现方法
}
// 然后在配置类中手动注册
2.2 过滤器生命周期详解
-
初始化(init):容器启动时调用,只执行一次
- 适合加载资源、初始化配置
-
执行(doFilter):每次请求都会调用
- 核心处理方法
-
销毁(destroy):容器关闭时调用
- 适合释放资源、保存状态
日常例子:就像一家餐厅:
- 开业前准备(init):购买食材、布置桌椅
- 接待顾客(doFilter):每位顾客的点单、上菜流程
- 打烊(destroy):清理厨房、结算账目
2.3 过滤器执行顺序控制
在Spring Boot中,控制过滤器执行顺序有两种方式:
-
使用@Order注解:
@Component @Order(1) // 数字越小优先级越高 public class FirstFilter implements Filter { ... }
-
使用FilterRegistrationBean设置order:
registration.setOrder(1);
执行顺序规则:
- order值越小,优先级越高
- 相同order值的情况下,过滤器名称字母顺序靠前的先执行
三、过滤器高级应用
3.1 请求参数处理
public class ParamsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 包装请求对象以便多次读取body
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
// 记录请求参数
System.out.println("请求URI: " + requestWrapper.getRequestURI());
System.out.println("请求方法: " + requestWrapper.getMethod());
// 获取GET参数
System.out.println("GET参数: " + requestWrapper.getParameterMap());
// 获取POST JSON参数
byte[] body = requestWrapper.getContentAsByteArray();
if (body.length > 0) {
String bodyStr = new String(body, requestWrapper.getCharacterEncoding());
System.out.println("POST参数: " + bodyStr);
}
chain.doFilter(requestWrapper, response);
}
}
3.2 响应数据处理
public class ResponseWrapperFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 包装响应对象
ContentCachingResponseWrapper responseWrapper =
new ContentCachingResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, responseWrapper);
// 获取响应数据
byte[] responseArray = responseWrapper.getContentAsByteArray();
String responseStr = new String(responseArray, responseWrapper.getCharacterEncoding());
System.out.println("响应状态: " + responseWrapper.getStatus());
System.out.println("响应内容: " + responseStr);
// 必须将内容写回客户端
responseWrapper.copyBodyToResponse();
}
}
3.3 跨域处理过滤器
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 设置跨域头
httpResponse.setHeader("Access-Control-Allow-Origin", "*");
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
httpResponse.setHeader("Access-Control-Max-Age", "3600");
httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, Content-Type");
// 预检请求直接返回
if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
httpResponse.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(request, response);
}
}
3.4 权限认证过滤器
public class AuthFilter implements Filter {
private static final Set<String> WHITE_LIST = Set.of("/login", "/register");
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String uri = httpRequest.getRequestURI();
// 白名单直接放行
if (WHITE_LIST.contains(uri)) {
chain.doFilter(request, response);
return;
}
// 检查token
String token = httpRequest.getHeader("Authorization");
if (token == null || !validateToken(token)) {
httpResponse.setContentType("application/json");
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.getWriter().write("{\"code\":401,\"message\":\"未授权\"}");
return;
}
chain.doFilter(request, response);
}
private boolean validateToken(String token) {
// 实际项目中这里应该实现token验证逻辑
return token != null && token.startsWith("Bearer ");
}
}
四、过滤器常见问题与解决方案
4.1 过滤器执行顺序问题
问题场景:多个过滤器需要按特定顺序执行
解决方案:
- 明确设置每个过滤器的order值
- 使用FilterRegistrationBean注册时指定顺序
// @Configuration 注解表明这是一个配置类,Spring 会处理该类中的 @Bean 方法,
// 将这些方法返回的对象注册到 Spring 容器中。
@Configuration
public class MultiFilterConfig {
// @Bean 注解将该方法返回的 FilterRegistrationBean<FilterA> 对象注册到 Spring 容器。
// 此方法用于配置和注册 FilterA 过滤器。
@Bean
public FilterRegistrationBean<FilterA> filterA() {
// 创建一个 FilterRegistrationBean<FilterA> 实例,用于注册 FilterA 过滤器。
FilterRegistrationBean<FilterA> registration = new FilterRegistrationBean<>();
// 为 FilterRegistrationBean 设置要注册的过滤器实例,这里创建了一个新的 FilterA 实例。
registration.setFilter(new FilterA());
// 添加过滤器的 URL 匹配模式,"/*" 表示该过滤器会对所有的请求 URL 进行过滤。
registration.addUrlPatterns("/*");
// 设置过滤器的执行顺序,值越小,执行优先级越高,这里设置为 1,意味着 FilterA 会先执行。
registration.setOrder(1);
// 返回配置好的 FilterRegistrationBean<FilterA> 实例。
return registration;
}
// @Bean 注解将该方法返回的 FilterRegistrationBean<FilterB> 对象注册到 Spring 容器。
// 此方法用于配置和注册 FilterB 过滤器。
@Bean
public FilterRegistrationBean<FilterB> filterB() {
// 创建一个 FilterRegistrationBean<FilterB> 实例,用于注册 FilterB 过滤器。
FilterRegistrationBean<FilterB> registration = new FilterRegistrationBean<>();
// 为 FilterRegistrationBean 设置要注册的过滤器实例,这里创建了一个新的 FilterB 实例。
registration.setFilter(new FilterB());
// 添加过滤器的 URL 匹配模式,"/*" 表示该过滤器会对所有的请求 URL 进行过滤。
registration.addUrlPatterns("/*");
// 设置过滤器的执行顺序,这里设置为 2,意味着在 FilterA 执行后,FilterB 才会执行。
registration.setOrder(2);
// 返回配置好的 FilterRegistrationBean<FilterB> 实例。
return registration;
}
}
4.2 请求body多次读取问题
问题场景:流只能读取一次,后续读取会为空
解决方案:使用请求包装类
public class RepeatReadFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 包装请求对象
ContentCachingRequestWrapper requestWrapper =
new ContentCachingRequestWrapper((HttpServletRequest) request);
chain.doFilter(requestWrapper, response);
}
}
4.3 性能监控过滤器示例
public class PerformanceFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
long startTime = System.currentTimeMillis();
try {
chain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
HttpServletRequest httpRequest = (HttpServletRequest) request;
System.out.printf("请求 %s %s 耗时 %d ms%n",
httpRequest.getMethod(),
httpRequest.getRequestURI(),
duration);
// 可以将耗时记录到日志系统或监控系统
Metrics.record(httpRequest.getRequestURI(), duration);
}
}
}
五、Spring Boot过滤器最佳实践
5.1 过滤器命名规范
建议采用功能+Filter
的命名方式,例如:
LoggingFilter
:日志记录AuthFilter
:权限验证CorsFilter
:跨域处理
5.2 过滤器职责单一原则
每个过滤器应该只关注一个特定功能,不要在一个过滤器中实现太多不相关的逻辑。
不好的做法:
// 一个过滤器同时处理日志、权限和跨域
public class AllInOneFilter implements Filter {
// 不推荐
}
好的做法:
// 分别实现不同的过滤器
public class LoggingFilter implements Filter { ... }
public class AuthFilter implements Filter { ... }
public class CorsFilter implements Filter { ... }
5.3 过滤器异常处理
public class ExceptionHandlerFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (BusinessException e) {
handleBusinessException((HttpServletResponse) response, e);
} catch (Exception e) {
handleUnexpectedException((HttpServletResponse) response, e);
}
}
private void handleBusinessException(HttpServletResponse response,
BusinessException e) throws IOException {
response.setContentType("application/json");
response.setStatus(e.getStatusCode());
response.getWriter().write(e.toJson());
}
private void handleUnexpectedException(HttpServletResponse response,
Exception e) throws IOException {
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("{\"error\":\"服务器内部错误\"}");
// 应该记录日志
log.error("未处理的异常", e);
}
}
六、Spring Boot过滤器与Spring MVC的集成
6.1 过滤器与拦截器的执行顺序
理解过滤器与拦截器的执行顺序对调试非常重要:
HTTP请求
↓
Servlet过滤器(Filter)
↓
DispatcherServlet
↓
Spring拦截器(Interceptor) preHandle
↓
Controller方法
↓
Spring拦截器(Interceptor) postHandle
↓
视图渲染(如果有)
↓
Spring拦截器(Interceptor) afterCompletion
↓
Servlet过滤器(Filter)
↓
HTTP响应
6.2 在过滤器中获取Spring Bean
默认情况下,过滤器不是由Spring管理的,无法直接使用@Autowired。有几种解决方案:
方案1:使用SpringBeanAutowiringSupport
public class SpringAwareFilter implements Filter {
@Autowired
private UserService userService;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
filterConfig.getServletContext());
}
// ... 其他方法
}
方案2:通过FilterRegistrationBean注入
@Configuration
public class FilterConfig {
@Autowired
private UserService userService;
@Bean
public FilterRegistrationBean<SpringAwareFilter> springAwareFilter() {
FilterRegistrationBean<SpringAwareFilter> registration =
new FilterRegistrationBean<>();
registration.setFilter(new SpringAwareFilter(userService));
// ... 其他配置
return registration;
}
}
七、实战:构建一个完整的API网关过滤器
让我们通过一个完整的例子,实现一个API网关常用的功能:
public class ApiGatewayFilter implements Filter {
private static final Set<String> WHITE_LIST = Set.of("/api/auth/login", "/api/auth/register");
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 1. 包装请求和响应以便多次读取
ContentCachingRequestWrapper requestWrapper =
new ContentCachingRequestWrapper(httpRequest);
ContentCachingResponseWrapper responseWrapper =
new ContentCachingResponseWrapper(httpResponse);
// 2. 记录请求开始时间
long startTime = System.currentTimeMillis();
try {
// 3. 跨域处理
handleCors(httpRequest, httpResponse);
// 4. 白名单检查
if (isWhiteList(httpRequest.getRequestURI())) {
chain.doFilter(requestWrapper, responseWrapper);
return;
}
// 5. 认证检查
if (!checkAuth(httpRequest)) {
sendErrorResponse(httpResponse, 401, "未授权");
return;
}
// 6. 限流检查
if (!rateLimit(httpRequest)) {
sendErrorResponse(httpResponse, 429, "请求过于频繁");
return;
}
// 7. 继续处理请求
chain.doFilter(requestWrapper, responseWrapper);
} catch (Exception e) {
// 8. 异常处理
handleException(e, httpResponse);
} finally {
// 9. 记录请求日志
logRequest(requestWrapper, responseWrapper, startTime);
// 10. 确保响应被写回客户端
responseWrapper.copyBodyToResponse();
}
}
private boolean isWhiteList(String uri) {
return WHITE_LIST.contains(uri);
}
private boolean checkAuth(HttpServletRequest request) {
String token = request.getHeader("Authorization");
// 实际项目中这里应该实现token验证逻辑
return token != null && token.startsWith("Bearer ");
}
private boolean rateLimit(HttpServletRequest request) {
String ip = request.getRemoteAddr();
// 简单的限流逻辑,实际项目应该使用Redis等实现
return true;
}
private void handleCors(HttpServletRequest request, HttpServletResponse response) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "*");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
}
}
private void sendErrorResponse(HttpServletResponse response, int code, String message)
throws IOException {
response.setContentType("application/json");
response.setStatus(code);
response.getWriter().write(String.format("{\"code\":%d,\"message\":\"%s\"}", code, message));
}
private void handleException(Exception e, HttpServletResponse response)
throws IOException {
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("{\"code\":500,\"message\":\"服务器内部错误\"}");
// 应该记录日志
System.err.println("处理请求时发生异常: " + e.getMessage());
}
private void logRequest(ContentCachingRequestWrapper request,
ContentCachingResponseWrapper response,
long startTime) {
long duration = System.currentTimeMillis() - startTime;
String requestBody = new String(request.getContentAsByteArray(),
request.getCharacterEncoding());
String responseBody = new String(response.getContentAsByteArray(),
response.getCharacterEncoding());
System.out.printf("""
===== 请求日志 =====
方法: %s
路径: %s
状态: %d
耗时: %dms
请求体: %s
响应体: %s
==================
%n""",
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
duration,
requestBody,
responseBody);
}
}
八、过滤器性能优化
8.1 过滤器链优化建议
- 减少不必要的过滤器:每个过滤器都会带来性能开销
- 合理设置URL模式:避免过于宽泛的urlPatterns
- 异步处理:对于耗时操作考虑使用异步过滤器
8.2 异步过滤器示例
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class AsyncFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 开启异步上下文
AsyncContext asyncContext = request.startAsync();
// 提交到线程池处理
CompletableFuture.runAsync(() -> {
try {
// 模拟耗时操作
Thread.sleep(1000);
// 继续过滤器链
chain.doFilter(asyncContext.getRequest(), asyncContext.getResponse());
// 完成异步处理
asyncContext.complete();
} catch (Exception e) {
asyncContext.complete();
throw new RuntimeException(e);
}
});
}
}
九、常见面试问题与解答
Q1: 过滤器和拦截器有什么区别?
答案:
参见本文1.2节的对比表格,重点从所属规范、执行位置、依赖框架、使用场景等方面回答。
Q2: 如何解决过滤器中的RequestBody只能读取一次的问题?
答案:
使用ContentCachingRequestWrapper包装原始请求,示例见3.1节。
Q3: 如何控制多个过滤器的执行顺序?
答案:
可以通过@Order注解或FilterRegistrationBean的setOrder方法设置顺序,数字越小优先级越高,详见2.3节。
Q4: 在过滤器中如何获取Spring容器中的Bean?
答案:
有几种方案:
- 使用SpringBeanAutowiringSupport(见6.2节方案1)
- 通过FilterRegistrationBean注入(见6.2节方案2)
- 实现ApplicationContextAware接口获取上下文
十、总结
Spring Boot过滤器是Web开发中强大的工具,本文从基础到高级全面介绍了过滤器的各个方面:
- 基础概念:理解过滤器是什么及其生命周期
- 创建方式:三种创建和注册过滤器的方法
- 高级应用:请求/响应处理、跨域、认证等实战场景
- 常见问题:执行顺序、Body读取、异常处理等解决方案
- 最佳实践:命名规范、职责单一、性能优化等建议
- 完整示例:API网关过滤器的完整实现
关注不关注,你自己决定(但正确的决定只有一个)。
喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!