SpringBoot过滤器详解与项目实战

一、过滤器基础概念

  • 什么是过滤器?​

    • 过滤器(Filter)是Java Web开发中的核心组件,位于客户端与服务器资源之间,能够对HTTP请求和响应进行预处理和后处理
    • 类比安检门:所有进入(请求)和离开(响应)应用的数据都要经过过滤器的检查和处理
  • 过滤器核心特性

    • 请求拦截:可拦截进入应用的HTTP请求
    • 双向处理:既能处理请求(Request),也能处理响应(Response)
    • 链式调用:多个过滤器可形成处理链
    • 生命周期管理:包含初始化(init)、执行(doFilter)和销毁(destroy)三个阶段
  • 过滤器与拦截器区别

    • 归属不同:过滤器是Servlet规范的一部分,拦截器是Spring MVC组件
    • 执行时机:过滤器在Servlet之前执行,拦截器在DispatcherServlet之后执行
    • 依赖关系:过滤器不依赖Spring容器,拦截器依赖Spring容器

二、SpringBoot中实现过滤器的三种方式

  • 方式1:使用@Component注解(最简单)​

    less 复制代码
    @Component
    @WebFilter(urlPatterns = "/*")
    public class SimpleFilter implements Filter {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, 
                           FilterChain chain) throws IOException, ServletException {
            System.out.println("【简单过滤器】请求进入");
            chain.doFilter(request, response);
            System.out.println("【简单过滤器】响应返回");
        }
    }
    • 需在启动类添加@ServletComponentScan
  • 方式2:使用FilterRegistrationBean(更灵活)​

    typescript 复制代码
    @Configuration
    public class FilterConfig {
        @Bean
        public FilterRegistrationBean<TimeCostFilter> timeCostFilter() {
            FilterRegistrationBean<TimeCostFilter> registration = new FilterRegistrationBean<>();
            registration.setFilter(new TimeCostFilter());
            registration.addUrlPatterns("/*");
            registration.setOrder(1); // 设置执行顺序
            return registration;
        }
    }
    • 可精确控制URL匹配、执行顺序等
  • 方式3:实现Filter接口并手动注册(最原始)​

    • 传统Servlet配置方式,适用于非Spring环境

三、过滤器生命周期与执行顺序

  • 生命周期详解

    • init():容器启动时调用,适合加载资源、初始化配置
    • doFilter():每次请求都会调用,核心处理方法
    • destroy():容器关闭时调用,适合释放资源
  • 执行顺序控制

    • 通过FilterRegistrationBean.setOrder()@Order注解控制

    • 规则:

      • order值越小,优先级越高
      • 相同order值时,按bean注册顺序执行
      • 正向流程按order升序执行,逆向流程按order降序执行
  • 多过滤器配置示例

    typescript 复制代码
    @Configuration
    public class MultiFilterConfig {
        @Bean
        public FilterRegistrationBean<FirstFilter> firstFilter() {
            FilterRegistrationBean<FirstFilter> registration = new FilterRegistrationBean<>();
            registration.setFilter(new FirstFilter());
            registration.addUrlPatterns("/*");
            registration.setOrder(1); // 最高优先级
            return registration;
        }
    
        @Bean
        public FilterRegistrationBean<SecondFilter> secondFilter() {
            registration.setFilter(new SecondFilter());
            registration.setOrder(2); // 次高优先级
            return registration;
        }
    }

四、过滤器高级应用场景

  • 权限验证过滤器

    ini 复制代码
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String token = httpRequest.getHeader("Authorization");
    
        if(!validateToken(token)) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
            httpResponse.getWriter().write("Unauthorized");
            return;
        }
        chain.doFilter(request, response);
    }
    • 检查Token或Session,阻止未授权访问
  • 请求日志记录过滤器

    vbscript 复制代码
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        log.info("Request: {} {}, Params: {}", 
                 httpRequest.getMethod(),
                 httpRequest.getRequestURI(),
                 httpRequest.getQueryString());
    
        ContentCachingRequestWrapper wrappedRequest = 
            new ContentCachingRequestWrapper(httpRequest);
        chain.doFilter(wrappedRequest, response);
    
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        log.info("Response Status: {}", httpResponse.getStatus());
    }
    • 记录请求和响应信息用于监控和调试
  • 跨域处理过滤器

    vbscript 复制代码
    public void doFilter(ServletRequest req, ServletResponse res, 
                        FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", 
                          "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", 
                          "Content-Type, Authorization");
        chain.doFilter(req, res);
    }
    • 解决前端跨域请求问题
  • 性能监控过滤器

    ini 复制代码
    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);
        }
    }
    • 统计请求处理时间,用于性能优化

五、常见问题与解决方案

  • 过滤器执行顺序问题

    • 问题场景:多个过滤器需要按特定顺序执行
    • 解决方案:明确设置每个过滤器的order值,使用FilterRegistrationBean注册时指定顺序
  • 请求body多次读取问题

    • 问题场景:流只能读取一次,后续读取会为空
    • 解决方案:使用请求包装类ContentCachingRequestWrapper
    vbscript 复制代码
    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);
        }
    }
  • 在过滤器中获取Spring Bean

    • 默认情况下,过滤器不是由Spring管理的,无法直接使用@Autowired

    • 解决方案1:使用SpringBeanAutowiringSupport

      java 复制代码
      public class SpringAwareFilter implements Filter {
          @Autowired
          private UserService userService;
      
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
              SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
                  filterConfig.getServletContext());
          }
      }
    • 解决方案2:通过FilterRegistrationBean注入

      typescript 复制代码
      @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网关过滤器

java 复制代码
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");
        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", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
    }
}
  • 该过滤器实现了API网关常见的功能:跨域处理、白名单检查、认证检查、限流等

七、最佳实践与性能优化

  • 过滤器命名规范

    • 建议采用功能+Filter的命名方式,例如:

      • LoggingFilter:日志记录
      • AuthFilter:权限验证
      • CorsFilter:跨域处理
  • 过滤器职责单一原则

    • 每个过滤器应该只关注一个特定功能

    • 不好的做法:

      java 复制代码
      // 一个过滤器同时处理日志、权限和跨域
      public class AllInOneFilter implements Filter {
          // 不推荐
      }
    • 好的做法:

      java 复制代码
      // 分别实现不同的过滤器
      public class LoggingFilter implements Filter { ... }
      public class AuthFilter implements Filter { ... }
      public class CorsFilter implements Filter { ... }
  • 过滤器异常处理

    vbscript 复制代码
    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);
        }
    }
    • 在过滤器中妥善处理异常,避免影响主流程
  • 性能优化建议

    • 避免在过滤器中执行耗时操作
    • 合理设置过滤路径,不要过度拦截
    • 过滤器是单例的,注意实例变量的线程安全
    • 推荐使用局部变量而非成员变量
相关推荐
JIngJaneIL1 小时前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
小信啊啊2 小时前
Go语言切片slice
开发语言·后端·golang
Victor3564 小时前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易4 小时前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
Kiri霧4 小时前
Range循环和切片
前端·后端·学习·golang
WizLC4 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Victor3564 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
爬山算法4 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
白宇横流学长5 小时前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端
Python编程学习圈5 小时前
Asciinema - 终端日志记录神器,开发者的福音
后端