Skip to content

Spring 中的过滤器、过滤器链、拦截器与 DispatcherServlet

很多人第一次学 Spring Web 主线时,很容易把下面这些词混在一起:

  1. Filter
  2. 过滤器链
  3. Interceptor
  4. DispatcherServlet

它们都和“请求怎么进来、怎么被处理”有关,但职责并不一样。

这条主线真正要回答的是:一个 HTTP 请求进入 Spring 应用后,先经过谁,再交给谁,哪些逻辑适合放在请求入口层,哪些逻辑适合放在 MVC 处理层。

这篇文章重点讲:

  1. Filter 到底是什么
  2. 过滤器链在控制什么
  3. InterceptorFilter 有什么区别
  4. DispatcherServlet 在整条链路中处于什么位置
  5. Spring Security 为什么严重依赖过滤器链
  6. 工程上应该怎么选、怎么落代码

1. 先建立一个整体认识

如果先不急着背接口名,可以把一条请求的主线理解成:

  1. 请求先进入 Servlet 容器
  2. 先经过一组 Filter
  3. 再进入 DispatcherServlet
  4. DispatcherServlet 再把请求分发给具体 Controller
  5. Controller 执行前后,可能还会经过 Interceptor

看一张总流程图:

mermaid
flowchart TD
    A[HTTP 请求进入应用] --> B[Servlet Filter 1]
    B --> C[Servlet Filter 2]
    C --> D[Spring Security 过滤器链]
    D --> E[DispatcherServlet]
    E --> F[HandlerInterceptor preHandle]
    F --> G[Controller 方法]
    G --> H[HandlerInterceptor postHandle]
    H --> I[视图解析或响应写出]
    I --> J[HandlerInterceptor afterCompletion]
    J --> K[HTTP 响应返回]

这张图最重要的意思是:

  1. Filter 更靠近 Servlet 容器入口
  2. Interceptor 更靠近 Spring MVC 方法调用链
  3. DispatcherServlet 是 Spring MVC 的统一调度中心

2. Filter 到底是什么

Filter 可以看成 Servlet 规范层面的请求过滤器

它解决的是 请求在真正进入业务处理之前,是否要先统一做一层预处理、包装、校验或拦截

它最常见的能力通常包括:

  1. 请求日志
  2. 字符编码处理
  3. token 提取
  4. 跨域处理
  5. 请求包装
  6. 黑名单 / 白名单拦截

这里要特别注意:

Filter 不是 Spring MVC 独有的概念。

它更靠近 Servlet 容器这一层,所以即使不用 Spring MVC,Servlet Web 应用里也会有 Filter

2.1 一个最简单的 Filter 示例

java
@Component
public class TraceFilter implements Filter {

    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        long start = System.currentTimeMillis();
        try {
            // 放行给后续过滤器或目标处理逻辑
            chain.doFilter(request, response);
        } finally {
            System.out.println(httpRequest.getRequestURI()
                    + " cost=" + (System.currentTimeMillis() - start));
        }
    }
}

这里真正关键的动作是:chain.doFilter(request, response)

它的意思不是“当前过滤器结束”,而是:把请求继续交给后面的过滤器链或最终目标处理逻辑。

如果不调用它,请求通常就不会继续往下走。


3. 过滤器链到底在说什么

单个 Filter 不难理解,真正容易混的是“过滤器链”。

过滤器链可以看成 多个 Filter 按顺序串起来,共同处理同一条请求

它解决的是 请求入口层的通用逻辑,不要都塞进一个超大过滤器里,而要拆成多段、按顺序协作

例如一条请求进来后,可能依次经过:

  1. 编码过滤器
  2. 请求日志过滤器
  3. 安全认证过滤器
  4. token 解析过滤器
  5. 异常包装过滤器

这时“谁先跑、谁后跑”就变得非常重要。

因为:前一个过滤器的处理结果,往往会直接影响后一个过滤器看到的请求状态。

3.1 为什么顺序很重要

例如:

  1. 如果 token 解析还没做,后面的权限过滤器可能拿不到用户身份
  2. 如果跨域响应头还没写,浏览器可能直接拦掉响应
  3. 如果日志过滤器放在过后位置,可能拿不到完整上下文

所以过滤器链真正要管的,不只是“有哪些过滤器”,而是:它们按什么顺序协作。


4. DispatcherServlet 在哪里

很多人学 Spring MVC 时,看到 DispatcherServlet 就以为:

所有请求一进应用,第一站就是 DispatcherServlet。

这个理解不够完整。DispatcherServlet 是 Spring MVC 的统一入口,但在它之前,请求通常已经先经过 Servlet Filter 链。可以直接这样理解分工:

  1. Filter:更靠近容器入口,处理更通用的 Web 请求问题
  2. DispatcherServlet:进入 Spring MVC 后的调度中心
  3. Interceptor:进入 MVC 处理链后,对 Controller 调用过程做增强

所以:

FilterDispatcherServlet 外面,InterceptorDispatcherServlet 里面。


5. Interceptor 又是什么

Interceptor 可以看成 Spring MVC 提供的方法调用拦截器

它解决的是 当请求已经进入 Spring MVC 体系后,是否需要在 Controller 执行前后插入一些逻辑

它更常见的场景通常包括:

  1. 登录态校验
  2. 接口访问审计
  3. 权限补充判断
  4. 统一埋点
  5. 请求级上下文准备

5.1 一个最简单的 Interceptor 示例

java
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String token = request.getHeader("Authorization");
        if (token == null || token.isBlank()) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        return true;
    }

    @Override
    public void afterCompletion(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            Exception ex) throws Exception {
        System.out.println("request finished: " + request.getRequestURI());
    }
}

然后在配置里注册:

java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    private final LoginInterceptor loginInterceptor;

    public WebMvcConfig(LoginInterceptor loginInterceptor) {
        this.loginInterceptor = loginInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/login");
    }
}

这里要特别注意:

Interceptor 不会拦到所有 Servlet 资源,它主要工作在 Spring MVC 的 Handler 调用链上。


6. FilterInterceptor 到底有什么区别

这是这条主线里最常见的问题。

看一张对比表:

对比维度FilterInterceptor
所属层级Servlet 规范层Spring MVC 层
执行位置DispatcherServlet 之前DispatcherServlet 之后、Controller 前后
主要对象ServletRequest / ServletResponseHttpServletRequest、Handler、ModelAndView 等
能否拦截静态资源通常可以通常主要针对 MVC Handler
更适合做什么编码、跨域、请求包装、统一入口校验登录态校验、权限补充、埋点、Controller 级增强
是否依赖 Spring MVC不一定

一句话压缩:

  1. Filter 更偏请求入口层
  2. Interceptor 更偏 MVC 方法调用层

6.1 怎么选更稳妥

工程上通常可以这样判断:

  1. 如果逻辑更靠近“请求进入应用的第一层”,优先考虑 Filter
  2. 如果逻辑明显依赖 Spring MVC 的 Handler、方法级语义,优先考虑 Interceptor

例如:

  1. 跨域、编码、请求包装,适合 Filter
  2. Controller 调用前的登录态校验、接口级审计,适合 Interceptor

7. 为什么 Spring Security 严重依赖过滤器链

很多人第一次接触 Spring Security 时会觉得:怎么一堆认证、授权、上下文、异常处理都和过滤器有关。

原因并不复杂。

因为 Spring Security 最核心的设计思想就是:在请求进入业务层之前,先通过一整套安全过滤器链把认证、授权、异常响应这些问题统一处理掉。

这意味着:

  1. 请求一进来,先经过安全过滤器链
  2. 过滤器提取 token、Session、用户名密码等认证信息
  3. 构建 SecurityContext
  4. 再决定是否允许继续访问后面的 Controller

也就是说:Spring Security 不是主要靠 Controller 注解撑起来的,而是主要靠过滤器链撑起来的。

7.1 SecurityFilterChain 到底是什么

SecurityFilterChain 可以看成 Spring Security 维护的一组安全过滤器规则集合

例如:

java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/login").permitAll()
                        .anyRequest().authenticated());

        return http.build();
    }
}

这里真正的重点不是某个方法名,而是:你在配置一条“哪些请求走什么安全规则”的过滤器链。


8. OncePerRequestFilter 为什么经常出现

在 Spring Security 或自定义认证场景里,经常会看到:OncePerRequestFilter

它可以看成 Spring 提供的一个更适合 Web 请求场景的过滤器基类,用来帮助你保证一次请求只执行一次核心过滤逻辑

它特别适合:

  1. token 解析
  2. JWT 校验
  3. 请求上下文初始化

例如一个简化版 JWT 过滤器:

java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            // 这里只做示意:真实项目里通常还要解析 token、查用户、构建权限信息
            request.setAttribute("currentUser", "tom");
        }

        filterChain.doFilter(request, response);
    }
}

要注意的是:它仍然是 Filter,只是 Spring 帮你封装成了更适合请求场景的基类。


9. Spring 里常见的过滤器类有哪些

很多人学到这里时,会继续碰到另一个问题:真正写项目时,经常会看到一堆 Filter 类名,它们分别是干什么的?

这时最容易乱的点,不是记不住类名,而是:不知道这些类分别属于 Servlet 层、Spring Web 层,还是 Spring Security 层。

可以把常见过滤器类按 3 组理解:

  1. 通用过滤器接口与基类
  2. Spring Web 常见基础过滤器
  3. Spring Security 入口与安全过滤器链相关类

9.1 通用过滤器接口与基类

看最常见的一组:

类 / 接口所属层次主要作用什么时候常见
FilterServlet 规范层定义过滤器的基础接口几乎所有 Web 过滤器的起点
GenericFilterBeanSpring Web 层Filter 包装成更适合 Spring Bean 管理的基类想写 Spring 风格过滤器,但不需要“一次请求只执行一次”语义时
OncePerRequestFilterSpring Web 层保证一次请求只执行一次核心过滤逻辑JWT、登录态、上下文初始化这类场景

这里最值得先记住的是:

  1. Filter 是最原始的 Servlet 接口
  2. GenericFilterBean 是 Spring 为了更方便接入 Bean 生命周期做的基类封装
  3. OncePerRequestFilter 则是在这个基础上,进一步强调“一次请求只做一次”

也就是说,它们不是彼此平级重复,而是:越往上越偏 Spring,越往下越偏 Servlet 原生接口。

9.2 GenericFilterBean 是什么

GenericFilterBean 可以看成 Spring 提供的一个过滤器基类,用来让 Filter 更自然地融入 Spring Bean 生命周期和配置体系

它解决的是 如果你既想写 Filter,又希望它能更方便地使用 Spring 容器能力,那么直接继承这个基类会比裸写 Filter 更顺手

例如:

java
@Component
public class TraceIdFilter extends GenericFilterBean {

    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        request.setAttribute("traceId", httpRequest.getRequestURI() + "-001");
        chain.doFilter(request, response);
    }
}

不过工程上如果你的过滤逻辑明确是“每个请求只执行一次”,通常还是更优先考虑:OncePerRequestFilter

9.3 Spring Web 里常见的基础过滤器

除了自己写过滤器,Spring Web 本身也提供了一些非常常见的基础过滤器类。

过滤器类主要作用常见场景
CharacterEncodingFilter统一请求和响应编码避免中文乱码
CorsFilter处理跨域请求前后端分离接口跨域
FormContentFilter让表单数据在非 POST 场景下也能更方便参与参数绑定PUTPATCH 等表单提交
HiddenHttpMethodFilter让表单通过隐藏字段模拟 PUTDELETE 等方法传统服务端页面表单
ForwardedHeaderFilter处理反向代理转发头Nginx、网关、负载均衡之后恢复原始协议和地址
ShallowEtagHeaderFilter生成浅层 ETag 响应头简单缓存协商场景

这些类最容易讲混的一点是:它们大多不是在做认证授权,而是在做 Web 基础设施层的通用处理。

例如:

  1. CharacterEncodingFilter 解决编码一致性
  2. CorsFilter 解决浏览器跨域
  3. ForwardedHeaderFilter 解决代理之后请求头还原

也就是说,它们更像:请求进入业务之前的一层通用环境整理。

9.4 Spring Security 相关的过滤器类怎么理解

到了安全场景,常见类名又会变一组。

这时最先要分清的通常是:

主要作用
DelegatingFilterProxy把 Servlet 容器中的 Filter 调用桥接到 Spring 容器里的 Bean
FilterChainProxySpring Security 内部真正管理多条安全过滤器链的核心代理
UsernamePasswordAuthenticationFilter处理表单登录认证
BasicAuthenticationFilter处理 HTTP Basic 认证
BearerTokenAuthenticationFilter处理 Bearer Token / OAuth2 资源服务器认证
ExceptionTranslationFilter统一把认证、授权异常转换成 HTTP 响应

这里最该注意的是:

  1. DelegatingFilterProxy 更像入口桥接器
  2. FilterChainProxy 更像安全过滤器总调度中心
  3. 后面那些认证、授权类过滤器,才是具体做安全处理的执行组件

如果你想把这组类再往下细看,可以接着看:

9.5 工程上该怎么选

如果把这些常见过滤器类放到实际项目里,可以这样判断:

  1. 如果你只是要实现最基础的 Servlet 过滤逻辑,可以直接实现 Filter
  2. 如果你希望它更自然地接入 Spring Bean 管理,可以优先考虑 GenericFilterBean
  3. 如果你做的是 JWT、登录态、请求上下文这类一次请求只应执行一次的逻辑,优先考虑 OncePerRequestFilter
  4. 如果你处理的是编码、跨域、反向代理头、表单方法转换这些通用 Web 问题,优看 Spring 提供的基础过滤器
  5. 如果你处理的是认证、授权、安全异常,优先放到 Spring Security 过滤器链里,而不是自己随意拼过滤器顺序

10. Filter 声明后是怎么进入容器并装配进过滤器链的

很多人写完一个过滤器类之后,接着就会疑惑:这个类到底是怎么被容器发现的,又是怎么真正出现在请求过滤链里的?

这里最容易混的,其实是两个“容器”:

  1. Spring 容器:负责管理 Bean
  2. Servlet 容器:负责真正维护 Web 过滤器链

🌟 这两个不是一回事。

Filter 要真正参与 HTTP 请求处理,最终一定要被注册到 Servlet 容器维护的过滤器链里;但在 Spring 项目中,这个注册过程经常又会借助 Spring 容器完成。

10.1 先分清“被 Spring 管理”和“进入过滤器链”不是同一件事

一个过滤器类即使已经被 Spring 扫描成 Bean,也不等于它已经自动进了 Servlet 过滤器链。

真正要发生两件事:

  1. 过滤器对象要能被创建和管理
  2. 过滤器要被注册到 Web 容器,指定拦截路径、顺序和生效范围

对应到两个容器,可以直接这样分工:

  1. Spring 容器 解决“这个对象谁来创建、依赖怎么注入”
  2. Servlet 容器 解决“这个过滤器要不要拦请求、拦哪些请求、排在第几位”

10.2 最常见的几种注册方式

工程上最常见的方式通常有 4 种。

10.2.1 直接实现 Filter,再交给 Spring Boot 自动注册

在 Spring Boot 项目里,最常见的一种体验是:

  1. 你写一个 Filter 实现类
  2. 再把它交给 Spring 管理,例如加上 @Component
  3. Spring Boot 在启动阶段把它注册到 Servlet 容器

例如:

java
@Component
public class TraceFilter implements Filter {

    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
    }
}

这种方式最方便,但默认也意味着:你把过滤器 Bean 交给了 Spring,再由 Spring Boot 帮你完成注册。

它的优点是简单,缺点是当你要精细控制顺序、URL 模式、是否启用时,通常不如显式注册灵活。

10.2.2 使用 FilterRegistrationBean 显式注册

如果你想更清楚地控制过滤器怎么进入链路,最常见的工程写法就是:FilterRegistrationBean

它可以理解成 Spring Boot 提供的一个注册器,用来把某个过滤器明确注册到 Servlet 容器,并指定顺序、URL 模式等信息。

例如:

java
@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<TraceFilter> traceFilterRegistration(TraceFilter traceFilter) {
        FilterRegistrationBean<TraceFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(traceFilter);
        registration.addUrlPatterns("/api/*");
        registration.setOrder(10);
        registration.setName("traceFilter");
        return registration;
    }
}

这里最重要的不是 API 名,而是它在表达:

  1. 用哪个过滤器实例
  2. 拦哪些路径
  3. 在链路里排第几位
  4. 是否启用某个名字或特定配置

所以如果你问“Filter 是怎么装配进链路的”,FilterRegistrationBean 是最典型、最直观的答案之一。

10.2.3 使用 @WebFilter,交给 Servlet 组件扫描

另一种方式更偏 Servlet 原生风格,也就是:@WebFilter

例如:

java
@WebFilter(urlPatterns = "/api/*", filterName = "traceFilter")
public class TraceFilter implements Filter {

    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
    }
}

如果走这条路,通常还需要:@ServletComponentScan

这样 Spring Boot 才会去扫描这些 Servlet 组件注解。

这种方式更接近传统 Servlet 编程风格,但在 Spring Boot 项目里,很多时候大家更常用的还是:

  1. @Component
  2. FilterRegistrationBean

因为它们和 Spring Bean 管理体系结合得更自然。

10.2.4 Spring Security 里的注册方式更特殊

如果到了 Spring Security,事情又不一样了。

很多安全过滤器并不是你一个个手工塞进 Servlet 过滤器链的,而是:

  1. Servlet 容器先接入 DelegatingFilterProxy
  2. 它把请求转交给 Spring 容器里的安全过滤器代理 Bean
  3. 再由 FilterChainProxy 根据请求匹配某条 SecurityFilterChain
  4. 最后执行这条链里的多个安全过滤器

也就是说:Spring Security 更像是把一个总入口 Filter 挂进 Servlet 容器,再由这个总入口在 Spring 内部继续调度真正的安全过滤器链。

这也是为什么你在安全场景里经常不是直接问:这个 Filter 有没有注册进去

而是更常问:它有没有被加到 SecurityFilterChain 的正确位置

10.3 过滤器顺序是怎么决定的

过滤器能不能进链只是第一步,另一个真正影响行为的问题是:它排在谁前面,谁后面。

这很重要,因为:

  1. 编码过滤器通常要尽量靠前
  2. token 解析要在授权判断之前
  3. 异常包装过滤器要能包住后面的链路

常见的控制方式包括:

  1. FilterRegistrationBean#setOrder(...)
  2. Spring Bean 排序相关机制
  3. 在 Spring Security 里用 addFilterBeforeaddFilterAfteraddFilterAt

所以真正完整的问题不是“Filter 有没有注册”,而是“Filter 是怎么注册的,又被放在链路的哪个位置”。

10.4 一句话把装配过程串起来

如果把这条链压缩成一句话,可以记成:

  1. 普通 Web 过滤器:Spring 创建 Bean -> 注册到 Servlet 容器 -> 进入 Filter 链
  2. Spring Security 过滤器:Servlet 容器接入 DelegatingFilterProxy -> Spring 内部交给 FilterChainProxy -> 匹配 SecurityFilterChain -> 执行具体安全过滤器

真正需要记住的核心不是具体某个注解,而是:Filter 最终必须被 Servlet 容器链路接纳,Spring 只是经常帮助你完成这个注册与装配过程。


11. 一条请求到底怎么走

如果把前面这些角色真正串起来,可以按这条顺序理解:

  1. 浏览器或客户端发起 HTTP 请求
  2. 请求先进入 Servlet 容器
  3. 先经过容器里的 Filter
  4. 如果启用了 Spring Security,还会经过安全过滤器链
  5. 然后进入 DispatcherServlet
  6. DispatcherServlet 找到目标 Controller
  7. 在 Controller 调用前后,可能触发 Interceptor
  8. Controller 返回结果
  9. 响应再一路返回给客户端

所以最值得记住的一句话是:Filter 更外层,Interceptor 更内层,DispatcherServlet 是 Spring MVC 的调度中心。

11.1 一个请求到响应在 Spring 中的完整链路

如果我们不只想记住几个组件名,而是真的想看懂:一条请求为什么会先过过滤器,再进 DispatcherServlet,最后又是怎么把响应写回去的。

那可以按下面这条主线来理解。

mermaid
flowchart TD
    A[客户端发起 HTTP 请求] --> B[Tomcat 等 Servlet 容器接收请求]
    B --> C[Servlet Filter 链]
    C --> D[Spring Security 过滤器链]
    D --> E[DispatcherServlet 接管请求]
    E --> F[HandlerMapping 查找目标处理器]
    F --> G[HandlerAdapter 调用前准备]
    G --> H[Interceptor preHandle]
    H --> I[参数绑定与类型转换]
    I --> J[参数校验]
    J --> K[执行 Controller 方法]
    K --> L[调用 Service 与 DAO]
    L --> M[返回处理结果]
    M --> N[Interceptor postHandle]
    N --> O[视图解析或消息转换写回]
    O --> P[Interceptor afterCompletion]
    P --> Q[Filter 链返回]
    Q --> R[客户端收到 HTTP 响应]
    J --> S[异常解析器处理异常]
    K --> S
    L --> S
    S --> O

这张图里最值得抓住的不是每个类名,而是三层分工:

  1. Servlet 容器层:负责接收 HTTP 请求,并先让 Filter 链处理
  2. Spring MVC 调度层:由 DispatcherServlet 负责把请求分发给正确的 Handler
  3. 业务执行层:真正调用 Controller、Service、DAO,最后再把结果转换成 HTTP 响应

11.2 把这条链路拆开看

11.2.1 请求进入 Servlet 容器

浏览器、前端或其他服务发出 HTTP 请求后,最先接住它的并不是 Controller,而是:

Tomcat、Jetty、Undertow 这类 Servlet 容器。

它们解决的是网络连接、请求接收、线程分配这些更底层的问题。

到了 Spring Boot 项目里,虽然我们平时主要写的是 Spring 代码,但真正接 HTTP 请求的底座,仍然是这层 Web 容器。

11.2.2 先过 Filter,再决定是否继续放行

请求进入容器后,通常会先进入 Filter 链。

这一步常做的事情包括:

  1. 字符编码处理
  2. 跨域响应头处理
  3. 请求日志记录
  4. token 初步提取
  5. 请求包装或统一上下文初始化

如果项目用了 Spring Security,那么安全过滤器链通常也会在这个阶段参与进来。

也就是说,很多认证、鉴权、匿名访问判断,并不是等到 Controller 里才做,而是在更前面的过滤器阶段就已经做掉了。

11.2.3 进入 DispatcherServlet

当前面的过滤器都放行后,请求才会真正进入 Spring MVC 的统一入口:DispatcherServlet

它可以理解成“总调度台”。

它自己通常不直接执行业务,而是负责:

  1. 找到这条请求该交给哪个 Controller 方法
  2. 选择合适的调用适配器
  3. 组织参数绑定、校验、返回值处理、异常处理

所以 DispatcherServlet 真正解决的是:把一个原始 HTTP 请求,组织成一次标准化的 Spring MVC 调用过程。

11.2.4 查找 Handler,并准备调用

DispatcherServlet 接到请求后,通常会先通过 HandlerMapping 查找目标 Handler。

例如:

  1. 请求路径是 /user/1
  2. HTTP 方法是 GET
  3. 最终匹配到 UserController#getUser

找到目标之后,再由 HandlerAdapter 负责把“找到 Handler”这件事转换成“真正可以执行的方法调用”。

更直白一点说:

  1. HandlerMapping 负责找人
  2. HandlerAdapter 负责真正把人叫起来干活

11.2.5 Interceptor 在 Controller 前后插入逻辑

在真正调用 Controller 之前,Spring MVC 还会先执行拦截器的 preHandle

这一层更适合做:

  1. 登录态补充判断
  2. 接口级审计
  3. 请求链路标识注入
  4. 与具体 Handler 强相关的上下文准备

如果 preHandle 返回 false,请求链路通常会被提前中止,不再继续调用 Controller。

如果成功放行,才会继续走后面的参数绑定与业务执行。

11.2.6 参数绑定、类型转换与校验

当请求已经确定要调用某个 Controller 方法时,Spring MVC 会继续做几件非常关键的事:

  1. 从 URL、Query、Header、Body 中取值
  2. 把字符串或 JSON 转成 Java 对象
  3. 执行类型转换
  4. 执行 @Valid@Validated 等参数校验

例如前端传来 JSON,请求体里的字段会先经过消息转换器反序列化成 Java 对象,再执行校验注解。

如果这一步失败,很多场景下甚至不会进入你的 Controller 方法,而是直接交给异常处理流程。

11.2.7 调用 Controller、Service、DAO

前面的准备都完成后,才会真正执行 Controller 方法。

Controller 通常只负责 Web 层职责,例如:

  1. 接收参数
  2. 调用 Service
  3. 返回统一响应对象

然后 Service 再负责业务编排,DAO 或 Repository 再负责数据库访问。

所以从职责划分上看,这一段链路通常是:Controller -> Service -> DAO

这也是为什么我们总说:Controller 不应该承载太重的业务逻辑。

11.2.8 返回值如何变成 HTTP 响应

业务方法执行完后,返回结果并不会原样直接丢给浏览器。

Spring MVC 还要继续做响应处理。

常见情况包括:

  1. 如果返回的是视图名,就走视图解析
  2. 如果返回的是对象,而 Controller 使用了 @ResponseBody@RestController,就会交给 HttpMessageConverter 转成 JSON
  3. 如果有拦截器,还会执行 postHandleafterCompletion

这里的 HttpMessageConverter 可以看成 负责在 Java 对象和 HTTP 消息体之间做转换的组件

也正因为有它,Controller 才可以很自然地返回一个 Java 对象,而不是手动拼接 JSON 字符串。

11.2.9 如果中间出错,会怎么走

真实工程里,一条请求并不总是顺利走到返回。

常见的异常可能出现在:

  1. 参数绑定失败
  2. 参数校验失败
  3. Controller 抛异常
  4. Service 或 DAO 抛异常

这时 Spring MVC 通常会把异常交给异常解析器或全局异常处理机制,例如 @ControllerAdvice

也就是说,异常处理本身也是请求链路的一部分,而不是“链路之外的补丁逻辑”。

11.2.10 响应写回之后,这条链才真正结束

当响应体被写回 HttpServletResponse 之后,请求才算接近尾声。

但这里还有两个容易被忽略的点:

  1. InterceptorafterCompletion 往往会在这个阶段执行清理逻辑
  2. 外层 Filterchain.doFilter() 返回后,也会继续执行自己的收尾代码

这就是为什么很多日志过滤器会把开始时间记在请求进入时,再在 finally 里打印整条请求耗时。

真正完整的链路,不只是“进来怎么走”,还包括:结果怎么返回,异常怎么处理,链路结束时谁负责收尾。


12. 工程上最容易踩的坑

12.1 误区一:FilterInterceptor 完全一样

它们都能做“前后拦截”,但工作层级不一样。

12.2 误区二:所有鉴权逻辑都塞进 Interceptor

如果已经使用 Spring Security,很多认证、授权、异常响应更适合交给安全过滤器链统一处理。

12.3 误区三:忘了调用 chain.doFilter

这会导致请求直接卡在当前过滤器,不再继续往后走。

12.4 误区四:以为 DispatcherServlet 是请求第一站

在它之前,通常已经先经过了一层或多层 Filter

12.5 误区五:把非常重的业务逻辑放在过滤器里

过滤器更适合做入口层的通用处理,不适合承载复杂业务。


13. 一份更实用的落地建议

如果你在 Spring Web 项目里真的要处理请求链路,下面这套习惯通常更稳:

  1. 跨域、编码、请求包装这类入口问题,优先放在 Filter
  2. 和 Controller 调用强相关的逻辑,优先考虑 Interceptor
  3. 认证、授权、异常响应等安全主线,尽量交给 Spring Security 过滤器链统一处理
  4. 自定义认证过滤器优先考虑 OncePerRequestFilter
  5. 明确链路顺序,不要把所有逻辑堆进一个入口组件里

14. 总结

把 Spring 里的过滤器、过滤器链、拦截器和 DispatcherServlet 压缩成最核心的几句话,就是:

  1. Filter 是更靠近 Servlet 容器入口的通用请求处理机制
  2. 过滤器链解决的是多个入口层逻辑如何按顺序协作
  3. DispatcherServlet 是 Spring MVC 的统一调度中心
  4. Interceptor 是 Spring MVC 内部对 Controller 调用过程的增强机制
  5. Spring Security 之所以强依赖过滤器链,是因为它要在请求进入业务层之前先统一完成安全处理

基于 VitePress 构建的个人技术笔记。