SpringBoot过滤器详解:从基础到高级实战

前言

作为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 过滤器生命周期详解

  1. 初始化(init):容器启动时调用,只执行一次

    • 适合加载资源、初始化配置
  2. 执行(doFilter):每次请求都会调用

    • 核心处理方法
  3. 销毁(destroy):容器关闭时调用

    • 适合释放资源、保存状态

日常例子:就像一家餐厅:

  • 开业前准备(init):购买食材、布置桌椅
  • 接待顾客(doFilter):每位顾客的点单、上菜流程
  • 打烊(destroy):清理厨房、结算账目

2.3 过滤器执行顺序控制

在Spring Boot中,控制过滤器执行顺序有两种方式:

  1. 使用@Order注解

    @Component
    @Order(1)  // 数字越小优先级越高
    public class FirstFilter implements Filter { ... }
    
  2. 使用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 过滤器执行顺序问题

问题场景:多个过滤器需要按特定顺序执行

解决方案

  1. 明确设置每个过滤器的order值
  2. 使用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 过滤器链优化建议

  1. 减少不必要的过滤器:每个过滤器都会带来性能开销
  2. 合理设置URL模式:避免过于宽泛的urlPatterns
  3. 异步处理:对于耗时操作考虑使用异步过滤器

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?

答案
有几种方案:

  1. 使用SpringBeanAutowiringSupport(见6.2节方案1)
  2. 通过FilterRegistrationBean注入(见6.2节方案2)
  3. 实现ApplicationContextAware接口获取上下文

十、总结

Spring Boot过滤器是Web开发中强大的工具,本文从基础到高级全面介绍了过滤器的各个方面:

  1. 基础概念:理解过滤器是什么及其生命周期
  2. 创建方式:三种创建和注册过滤器的方法
  3. 高级应用:请求/响应处理、跨域、认证等实战场景
  4. 常见问题:执行顺序、Body读取、异常处理等解决方案
  5. 最佳实践:命名规范、职责单一、性能优化等建议
  6. 完整示例:API网关过滤器的完整实现

关注不关注,你自己决定(但正确的决定只有一个)。

喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clf丶忆笙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值