SpringBoot 中 Filter 与 Interceptor 的生态位解析与 Servlet 拦截器实现

253 阅读11分钟
引言

在 SpringBoot 开发中,处理 HTTP 请求的拦截和过滤是常见需求。无论是权限校验、日志记录,还是请求预处理,Spring 提供了 FilterInterceptor 两种机制来实现拦截逻辑。然而,它们的职责、生态位以及在请求链路中的作用却常常让人混淆。本文将深入探讨 FilterInterceptor 在 SpringBoot 项目中的应用,分析它们在请求链路中的定位,并通过 FilterRegistrationBean 实现 Servlet 拦截器,帮助开发者更清晰地选择适合的工具。

正文
1. Filter 和 Interceptor 的基本概念

Filter 是 Java Servlet 规范的一部分,运行在 Web 容器(如 Tomcat、Jetty)中,独立于 Spring 框架。它通过 javax.servlet.Filter 接口实现,用于在请求到达 Servlet 或响应返回客户端之前进行预处理或后处理。典型场景包括编码转换、CORS 处理、请求日志等。

Interceptor 是 SpringMVC 的组件,通过 org.springframework.web.servlet.HandlerInterceptor 接口实现,运行在 SpringMVC 的 DispatcherServlet 内部。它更专注于处理 Spring 上下文中的请求,适合业务逻辑的拦截,如权限验证、事务管理等。

2. FilterRegistrationBean 实现 Servlet 拦截器

在 SpringBoot 中,注册一个 Filter 可以通过 @WebFilter 注解或 FilterRegistrationBean 实现。后者更灵活,支持动态配置过滤器顺序和 URL 模式。以下是一个示例:

@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean<LoggingFilter> loggingFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new LoggingFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        System.out.println("Request URI: " + req.getRequestURI());
        chain.doFilter(request, response);
    }
}

通过 FilterRegistrationBean,我们可以指定过滤器的执行顺序(setOrder)和匹配的 URL 模式(addUrlPatterns),实现对请求的拦截。

3. Filter 和 Interceptor 的生态位
  • Filter 的生态位Filter 位于 Web 容器的 Servlet 层,作用于整个请求链路的入口和出口。它可以处理非 Spring 管理的请求(如静态资源请求),适合跨应用的通用逻辑。
  • Interceptor 的生态位Interceptor 运行在 SpringMVC 的 DispatcherServlet 内部,仅处理 Spring 管理的请求。它能访问 Spring 上下文(如 @Autowired 注入的 Bean),适合业务相关的逻辑。
  • 选择依据:如果需要处理所有 HTTP 请求(包括非 Spring 路由),选择 Filter;如果需要深度集成 Spring 生态(如操作 ModelAndView),选择 Interceptor
4. 层级结构图

以下是请求链路的层级结构(文字描述,实际项目中可通过工具如 PlantUML 绘制):

[Client] --> [Web 容器(如 Tomcat)]
                |
                v
           [Filter Chain]
                |
                v
       [SpringBoot 应用(Servlet)]
                |
                v
      [DispatcherServlet(SpringMVC)]
                |
                v
           [Interceptor Chain]
                |
                v
           [Controller]
5. 总结

FilterInterceptor 各有其生态位,Filter 更贴近 Servlet 层,适合通用请求处理;Interceptor 更贴近 SpringMVC,适合业务逻辑处理。通过 FilterRegistrationBean,我们可以灵活注册 Servlet 拦截器,实现请求的动态管理。理解它们的层级关系和职责划分,是构建高效 SpringBoot 应用的关键。


2. 分析:通过 FilterRegistrationBean 实现 Servlet 拦截器

在 SpringBoot 项目中,FilterRegistrationBean 是一种程序化配置 Filter 的方式,相比 @WebFilter 注解,它提供了更高的灵活性。以下是实现步骤和关键点:

实现步骤

  1. 创建自定义 Filter:实现 javax.servlet.Filter 接口,重写 doFilter 方法。例如:
public class CustomFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化逻辑
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        System.out.println("Filtering request: " + req.getRequestURI());
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 销毁逻辑
    }
}
  1. 配置 FilterRegistrationBean:通过 @Bean 注解将 Filter 注册到 Spring 容器。
@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean<CustomFilter> customFilter() {
        FilterRegistrationBean<CustomFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new CustomFilter());
        registrationBean.addUrlPatterns("/api/*"); // 匹配路径
        registrationBean.setOrder(1); // 设置执行顺序
        registrationBean.setName("customFilter"); // 设置名称
        return registrationBean;
    }
}
  1. 启动应用:SpringBoot 会自动将注册的 Filter 加载到 Servlet 容器的过滤器链中。

关键点

  • URL 模式addUrlPatterns 支持 Ant 风格路径匹配,如 /*/api/**
  • 执行顺序:通过 setOrder 指定过滤器执行顺序,值越小越先执行。
  • 异步支持:可通过 setAsyncSupported(true) 启用异步请求处理。
  • 动态配置:相比 @WebFilterFilterRegistrationBean 支持运行时动态调整过滤器配置。

优势

  • 灵活性:支持条件化注册(如根据配置文件启用/禁用过滤器)。
  • 可维护性:将过滤器配置集中到 @Configuration 类中,便于管理。
  • 兼容性:与 Servlet 容器无缝集成,适用于嵌入式容器(如 Tomcat)和外部容器。

3. 深度分析:Filter 在请求链路中的生态位

3.1 请求链路的层级结构

为清晰理解 FilterInterceptor 的生态位,以下是 SpringBoot 应用中 HTTP 请求的处理链路(文字描述,实际可通过 PlantUML 绘制 UML 图):

[Client] --> [Web 容器(如 Tomcat、Jetty)]
                |
                v
           [Servlet Filter Chain]
                | (Filter1 -> Filter2 -> ...)
                v
       [SpringBoot 应用(Servlet 实例)]
                |
                v
      [DispatcherServlet(SpringMVC 核心)]
                |
                v
           [Interceptor Chain]
                | (Interceptor1 -> Interceptor2 -> ...)
                v
           [HandlerMapping]
                |
                v
           [Controller]
                |
                v
           [Response]

层级说明

  1. Web 容器:如 Tomcat,负责接收 HTTP 请求,初始化 Servlet 实例。
  2. Filter Chain:Servlet 规范定义的过滤器链,运行在容器层,所有 HTTP 请求(包括静态资源)都会经过。
  3. DispatcherServlet:SpringMVC 的核心 Servlet,负责路由分发和请求处理。
  4. Interceptor Chain:SpringMVC 提供的拦截器链,仅处理 DispatcherServlet 管理的请求。
  5. Controller:业务逻辑的最终处理者。

3.2 Filter 的生态位

  • 定义Filter 是 Servlet 规范的一部分,运行在 Web 容器中,独立于 Spring 框架。
  • 职责
    • 请求预处理:如设置字符编码、校验请求头、记录请求日志。
    • 响应后处理:如修改响应头、压缩响应内容。
    • 通用逻辑:处理所有 HTTP 请求,包括非 Spring 路由(如静态资源、其他 Servlet)。
  • 生命周期
    • init:容器启动时初始化。
    • doFilter:处理每次请求。
    • destroy:容器关闭时销毁。
  • 优势
    • 跨框架通用:不依赖 Spring,可用于任何 Servlet 应用。
    • 全局拦截:能拦截所有请求,包括非 SpringMVC 路由。
  • 局限性
    • 上下文隔离:无法直接访问 Spring 容器中的 Bean(如 @Autowired)。
    • 粒度较粗:不适合处理 SpringMVC 特有的对象(如 ModelAndView)。

3.3 Interceptor 的生态位

  • 定义Interceptor 是 SpringMVC 的组件,运行在 DispatcherServlet 内部。
  • 职责
    • 前置处理preHandle,在控制器执行前处理,如权限校验。
    • 后置处理postHandle,在控制器执行后、视图渲染前处理,如修改 ModelAndView
    • 完成处理afterCompletion,在响应完成后处理,如清理资源。
  • 生命周期
    • 通过 HandlerInterceptor 接口实现,注册到 WebMvcConfigurer
    • 仅在 DispatcherServlet 处理请求时生效。
  • 优势
    • 深度集成:可访问 Spring 上下文,直接操作 Spring 管理的对象。
    • 细粒度控制:能处理 SpringMVC 特有的流程(如视图渲染)。
  • 局限性
    • 作用范围有限:仅处理 SpringMVC 路由,无法拦截静态资源或其他 Servlet 请求。
    • 框架依赖:强耦合于 SpringMVC。

3.4 Filter vs Interceptor:生态位对比

特性FilterInterceptor
所属规范Servlet 规范SpringMVC 框架
运行层级Web 容器(Servlet 层)DispatcherServlet(SpringMVC 层)
拦截范围所有 HTTP 请求(包括静态资源)仅 SpringMVC 路由
Spring 集成需手动注入 Bean天然支持 Spring 上下文
典型场景编码转换、CORS、日志权限校验、事务管理、ModelAndView
生命周期方法init, doFilter, destroypreHandle, postHandle, afterCompletion

3.5 SpringBoot 应用的 Servlet 上下文

您提到“Spring 服务是在一个 Web 容器里运行的,所以是一个 Servlet”。这是正确的,但需要澄清:

  • SpringBoot 应用的 Servlet:SpringBoot 嵌入式 Web 容器(如 Tomcat)会加载一个 DispatcherServlet 作为核心 Servlet,负责处理所有 SpringMVC 路由。
  • Servlet 的角色DispatcherServlet 是 Servlet 规范的实现,SpringBoot 通过它将请求分发到控制器。所有 HTTP 请求都会先经过 Web 容器的过滤器链(Filter Chain),然后到达 DispatcherServlet
  • Filter 的定位FilterDispatcherServlet 之前运行,负责全局请求处理。InterceptorDispatcherServlet 内部运行,负责 SpringMVC 特有的逻辑。

4. 模拟面试官:深入拷打

以下以面试官视角,针对 FilterInterceptor 的实现、生态位、性能、异常处理等细节进行深入 3-4 层的分析,步步追问。

问题 1:FilterRegistrationBean 的底层实现原理是什么?

候选人回答FilterRegistrationBean 是一个 SpringBoot 提供的工具类,用于将 Filter 注册到 Servlet 容器。它通过 setFilter 设置过滤器实例,addUrlPatterns 指定匹配路径,setOrder 设置执行顺序,最终将过滤器注册到容器。

面试官追问

  1. SpringBoot 如何将 FilterRegistrationBean 注入到 Servlet 容器?
    • 回答:SpringBoot 在启动时会创建一个 ServletContextInitializerFilterRegistrationBean 实现了 ServletContextInitializer 接口。在 onStartup 方法中,它调用 ServletContext.addFilter 将过滤器注册到容器。
    • 追问ServletContext.addFilter 是如何与嵌入式容器(如 Tomcat)交互的?
      • 回答:SpringBoot 的嵌入式容器(如 TomcatServletWebServerFactory)会创建一个 TomcatContextServletContext.addFilter 最终调用 Tomcat 的 StandardContext.addFilterDefaddFilterMap,将过滤器添加到容器上下文。
      • 追问:如果有多个 FilterRegistrationBean,顺序如何保证?
        • 回答FilterRegistrationBeansetOrder 方法设置优先级,SpringBoot 在注册时会根据 Ordered 接口的 getOrder 方法排序,确保按顺序加载到容器。

问题 2:Filter 和 Interceptor 在性能上有什么差异?

候选人回答Filter 运行在 Servlet 容器层,拦截所有请求,性能开销可能较大;Interceptor 只处理 SpringMVC 路由,范围较小,可能更高效。

面试官追问

  1. 为什么 Filter 的性能开销可能更大?
    • 回答Filter 拦截所有 HTTP 请求,包括静态资源和其他 Servlet 请求,需要处理更多的请求类型,可能增加 CPU 和内存开销。
    • 追问:如果 Filter 只拦截特定路径,性能会改善吗?
      • 回答:是的,通过 addUrlPatterns 限制路径,Filter 只处理匹配的请求,减少不必要的处理。但仍需执行 Servlet 容器的过滤器链逻辑,相比 Interceptor 仍有额外开销。
      • 追问:Interceptor 在 DispatcherServlet 内部的性能优势具体体现在哪里?
        • 回答Interceptor 直接运行在 SpringMVC 的 HandlerExecutionChain 中,仅处理 Spring 路由,且能利用 Spring 的缓存(如 HandlerMapping 缓存),减少重复解析。同时,Interceptor 可直接访问 Spring 上下文,避免跨层调用。
        • 追问:如果 Filter 和 Interceptor 都执行复杂逻辑(如数据库查询),性能差异还明显吗?
          • 回答:如果逻辑复杂,性能瓶颈主要在业务逻辑(如 I/O 操作),FilterInterceptor 的层级差异对性能影响较小。但 Interceptor 仍因更少的请求处理范围和 Spring 集成优势略占优。

问题 3:Filter 和 Interceptor 的异常处理机制有何不同?

候选人回答Filter 的异常需要在 doFilter 中手动捕获,否则会抛到容器,可能导致 500 错误。Interceptor 的异常可由 SpringMVC 的 ExceptionHandler 统一处理。

面试官追问

  1. Filter 的异常如果抛到容器,会有什么后果?
    • 回答:异常抛到容器后,容器会返回 HTTP 500 错误,响应内容由容器决定(如 Tomcat 的默认错误页)。客户端无法获取自定义错误信息。
    • 追问:如何在 Filter 中实现统一的异常处理?
      • 回答:可以在 Filter 中使用 try-catch 捕获异常,设置响应状态码和内容。例如:
        try {
            chain.doFilter(request, response);
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.getWriter().write("Error: " + e.getMessage());
        }
        
      • 追问:Interceptor 的异常如何被 SpringMVC 捕获?
        • 回答Interceptor 运行在 DispatcherServletprocessDispatch 方法中,异常会被 DispatcherServlet 捕获,并交给 HandlerExceptionResolver 处理。开发者可通过 @ExceptionHandler 或自定义 HandlerExceptionResolver 实现统一异常处理。
        • 追问:如果 Filter 和 Interceptor 同时抛出异常,SpringMVC 还能否捕获?
          • 回答Filter 的异常在 DispatcherServlet 之前抛出,SpringMVC 无法捕获,需在 Filter 中处理。Interceptor 的异常则会被 SpringMVC 捕获并处理。

问题 4:Filter 和 Interceptor 的线程安全问题如何考虑?

候选人回答FilterInterceptor 都是单例的,需确保线程安全,避免在实例变量中存储请求相关的状态。

面试官追问

  1. Filter 的线程安全问题具体有哪些风险?
    • 回答Filter 是 Servlet 容器管理的单例,doFilter 方法会被多个请求线程并发调用。如果在 Filter 实例中定义可变成员变量(如计数器),可能导致数据竞争。
    • 追问:如何避免 Filter 的线程安全问题?
      • 回答:避免使用实例变量,所有状态都存储在方法局部变量或 ServletRequest 的属性中。例如:
        public class SafeFilter implements Filter {
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                    throws IOException, ServletException {
                // 使用局部变量
                String requestId = UUID.randomUUID().toString();
                request.setAttribute("requestId", requestId);
                chain.doFilter(request, response);
            }
        }
        
      • 追问:Interceptor 的线程安全问题与 Filter 有何不同?
        • 回答Interceptor 也是单例,线程安全问题类似。但由于 Interceptor 运行在 Spring 容器中,可通过 Spring 的 @Scope("prototype") 创建请求作用域实例,降低线程安全风险。不过,原型模式会增加内存开销,通常仍推荐单例模式并避免可变状态。
        • 追问:如果 Filter 或 Interceptor 需要共享状态(如缓存),如何保证线程安全?
          • 回答:使用线程安全的容器,如 ConcurrentHashMapCopyOnWriteArrayList。例如:
            public class CacheFilter implements Filter {
                private final ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
                @Override
                public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                        throws IOException, ServletException {
                    String key = ((HttpServletRequest) request).getRequestURI();
                    cache.putIfAbsent(key, "cached");
                    chain.doFilter(request, response);
                }
            }