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

    • 避免在过滤器中执行耗时操作
    • 合理设置过滤路径,不要过度拦截
    • 过滤器是单例的,注意实例变量的线程安全
    • 推荐使用局部变量而非成员变量
相关推荐
两万五千个小时3 小时前
LangChain 入门教程:构建你的第一个聊天机器人
后端
间彧4 小时前
SpringBoot如何通过拦截器实现接口限流
后端
间彧4 小时前
Spring Boot拦截器详解与项目实战
后端
绝无仅有4 小时前
面试技巧之Linux相关问题的解答
后端·面试·github
绝无仅有4 小时前
某跳动大厂 MySQL 面试题解析与总结实战
后端·面试·github
道可到4 小时前
阿里面试原题 面试通关笔记05 | 异常、泛型与反射——类型擦除的成本与优化
java·后端·面试
贾维斯Echo4 小时前
保姆级教程!华为昇腾NPU DeepSeek安装部署全流程!
后端
Postkarte不想说话4 小时前
Xfce4 鼠标滚轮滚动禁止获取焦点
后端
风雨同舟的代码笔记4 小时前
Linux环境下MySQL安装教程
后端