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

    • 避免在过滤器中执行耗时操作
    • 合理设置过滤路径,不要过度拦截
    • 过滤器是单例的,注意实例变量的线程安全
    • 推荐使用局部变量而非成员变量
相关推荐
鬼火儿5 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
cr7xin5 小时前
缓存三大问题及解决方案
redis·后端·缓存
间彧6 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧6 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧7 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧7 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧7 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧7 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧7 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang7 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构