原创

Spring Boot 拦截器使用全指南

一、拦截器的基本概念

在 Web 应用开发中,经常需要在请求处理前后执行一些通用逻辑,例如:

  • 登录鉴权
  • 请求日志记录
  • 接口调用统计
  • 参数校验
  • 统一响应处理
  • 接口限流

如果在每一个 Controller 方法中编写这些逻辑,不仅会造成代码重复,还会破坏业务代码的整洁性。

Spring Boot 基于 Spring MVC,提供了 HandlerInterceptor(拦截器)机制,用于在 请求进入 Controller 之前或响应返回之后执行统一处理逻辑

拦截器的执行流程如下:

客户端请求
      │
      ▼
DispatcherServlet
      │
      ▼
HandlerMapping
      │
      ▼
HandlerInterceptor.preHandle()
      │
      ▼
Controller 方法执行
      │
      ▼
HandlerInterceptor.postHandle()
      │
      ▼
视图渲染
      │
      ▼
HandlerInterceptor.afterCompletion()
      │
      ▼
响应返回客户端

拦截器属于 Spring MVC 体系,其作用范围仅限于 Controller 层请求处理流程


二、拦截器的核心接口 HandlerInterceptor

Spring MVC 拦截器核心接口为:

org.springframework.web.servlet.HandlerInterceptor

主要包含三个方法:

public interface HandlerInterceptor {

    boolean preHandle(HttpServletRequest request,
                      HttpServletResponse response,
                      Object handler) throws Exception;

    void postHandle(HttpServletRequest request,
                    HttpServletResponse response,
                    Object handler,
                    ModelAndView modelAndView) throws Exception;

    void afterCompletion(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler,
                         Exception ex) throws Exception;
}

1 preHandle

请求进入 Controller 之前执行

返回值决定是否继续执行后续流程:

  • true:继续执行 Controller
  • false:中断请求

示例:

@Override
public boolean preHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler) throws Exception {

    System.out.println("请求进入拦截器");

    return true;
}

典型使用场景:

  • 登录校验
  • token 校验
  • 接口签名验证
  • 参数检查
  • 防重复提交

2 postHandle

Controller 方法执行完成后,但 视图渲染之前执行

@Override
public void postHandle(HttpServletRequest request,
                       HttpServletResponse response,
                       Object handler,
                       ModelAndView modelAndView) throws Exception {

    System.out.println("Controller 执行完成");
}

常见用途:

  • 修改返回数据
  • 添加公共模型数据
  • 统一视图处理

如果接口返回 JSON(如 REST API),通常较少使用此方法。


3 afterCompletion

请求 完全结束后执行

@Override
public void afterCompletion(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            Exception ex) throws Exception {

    System.out.println("请求完成");
}

典型用途:

  • 请求耗时统计
  • 资源释放
  • 日志记录

三、Spring Boot 创建拦截器

创建拦截器只需要实现 HandlerInterceptor 接口。

示例:

package com.example.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        System.out.println("请求路径:" + request.getRequestURI());
        return true;
    }
}

如果需要实现多个方法:

public class ApiInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        long start = System.currentTimeMillis();
        request.setAttribute("startTime", start);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {

        long start = (long) request.getAttribute("startTime");
        long cost = System.currentTimeMillis() - start;

        System.out.println("接口耗时:" + cost + "ms");
    }
}

四、注册拦截器

Spring Boot 中必须通过 WebMvcConfigurer 注册拦截器。

示例:

package com.example.config;

import com.example.interceptor.LogInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new LogInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/login",
                        "/static/**"
                );
    }
}

关键方法:

方法 作用
addInterceptor 注册拦截器
addPathPatterns 拦截路径
excludePathPatterns 排除路径

五、拦截路径规则详解

Spring MVC 拦截路径支持 Ant 风格表达式。

常见规则如下:

规则 含义
/user 精确匹配
/user/* 匹配一层
/user/** 匹配任意层
/** 匹配全部路径

示例:

registry.addInterceptor(new LoginInterceptor())
        .addPathPatterns("/api/**")
        .excludePathPatterns(
                "/api/login",
                "/api/register"
        );

说明:

  • /api/** 拦截所有 API
  • /api/login 不拦截

六、拦截器执行顺序

当存在多个拦截器时,执行顺序由注册顺序决定。

示例:

registry.addInterceptor(new AInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new BInterceptor()).addPathPatterns("/**");

执行顺序:

A.preHandle
B.preHandle
Controller
B.postHandle
A.postHandle
B.afterCompletion
A.afterCompletion

特点:

  • preHandle:顺序执行
  • postHandle:逆序执行
  • afterCompletion:逆序执行

七、典型应用场景

1 登录鉴权拦截器

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        HttpSession session = request.getSession(false);

        if (session == null || session.getAttribute("user") == null) {

            response.setStatus(401);
            response.getWriter().write("未登录");

            return false;
        }

        return true;
    }
}

注册:

registry.addInterceptor(new LoginInterceptor())
        .addPathPatterns("/api/**")
        .excludePathPatterns("/api/login");

2 API 请求日志记录

public class RequestLogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {

        long start = System.currentTimeMillis();

        request.setAttribute("start", start);

        System.out.println("请求:" + request.getMethod() +
                " " + request.getRequestURI());

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) {

        long start = (long) request.getAttribute("start");
        long cost = System.currentTimeMillis() - start;

        System.out.println("耗时:" + cost + "ms");
    }
}

3 Token 校验拦截器

public class TokenInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        String token = request.getHeader("Authorization");

        if (token == null || !verifyToken(token)) {

            response.setStatus(401);
            response.getWriter().write("token 无效");

            return false;
        }

        return true;
    }

    private boolean verifyToken(String token) {

        return token.equals("test-token");
    }
}

八、拦截器与 Filter 的区别

Spring Boot 中还存在 Servlet Filter,与拦截器功能类似,但实现层不同。

对比 Filter Interceptor
规范 Servlet 规范 Spring MVC
作用范围 所有请求 仅 Controller
执行时机 DispatcherServlet 之前 Controller 之前
使用难度 较底层 更适合 Spring
获取 Bean 不方便 非常方便

执行顺序:

Filter
   ↓
DispatcherServlet
   ↓
Interceptor
   ↓
Controller

实际项目建议:

  • Filter:底层处理

    • 编码
    • 跨域
    • 请求包装
  • Interceptor:业务逻辑

    • 登录校验
    • 权限验证
    • 接口日志

九、Spring Boot 2 与 Spring Boot 3 区别

Spring Boot 2

包路径:

javax.servlet.*

示例:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

Spring Boot 3

由于升级到 Jakarta EE,包路径变为:

jakarta.servlet.*

示例:

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

这是最主要的变化。

拦截器接口本身没有变化:

HandlerInterceptor

仍然位于:

org.springframework.web.servlet

十、生产环境最佳实践

1 拦截器应保持轻量

拦截器运行在 所有请求路径上

不应执行:

  • 大量数据库查询
  • 复杂计算
  • 阻塞 IO

否则会显著影响接口性能。


2 使用 ThreadLocal 传递上下文

例如保存当前用户信息。

public class UserContext {

    private static final ThreadLocal<Long> USER_ID = new ThreadLocal<>();

    public static void set(Long userId) {
        USER_ID.set(userId);
    }

    public static Long get() {
        return USER_ID.get();
    }

    public static void clear() {
        USER_ID.remove();
    }
}

拦截器:

UserContext.set(userId);

afterCompletion:

UserContext.clear();

3 配合 Redis 做接口限流

例如:

用户访问接口
       ↓
拦截器
       ↓
Redis INCR
       ↓
超过阈值拒绝访问

示例:

String key = "rate:" + userId;

Long count = redisTemplate.opsForValue().increment(key);

if (count == 1) {
    redisTemplate.expire(key, 1, TimeUnit.SECONDS);
}

if (count > 10) {
    response.setStatus(429);
    return false;
}

4 统一接口日志系统

生产环境通常将接口日志写入:

  • Kafka
  • Elasticsearch
  • ClickHouse

结构示例:

字段 含义
trace_id 请求ID
uri 请求路径
method HTTP方法
user_id 用户ID
cost 耗时
ip 客户端IP

十一、常见问题

1 静态资源被拦截

解决:

.excludePathPatterns(
        "/css/**",
        "/js/**",
        "/images/**",
        "/webjars/**"
)

2 拦截器无法注入 Bean

原因:

new LoginInterceptor()

不是 Spring Bean。

解决:

@Bean
public LoginInterceptor loginInterceptor() {
    return new LoginInterceptor();
}

注册:

registry.addInterceptor(loginInterceptor());

3 @ResponseBody 返回被拦截

拦截器不会影响 JSON 返回。

但如果:

preHandle return false

必须手动写 response。


十二、完整项目结构示例

project
 ├─ config
 │   └─ WebMvcConfig.java
 │
 ├─ interceptor
 │   ├─ LoginInterceptor.java
 │   ├─ LogInterceptor.java
 │   └─ TokenInterceptor.java
 │
 ├─ controller
 │   └─ UserController.java
 │
 └─ Application.java

Controller 示例:

@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/test")
    public String test() {
        return "ok";
    }
}

十三、拦截器执行流程总结

Spring Boot 请求执行流程:

HTTP Request
      ↓
Filter
      ↓
DispatcherServlet
      ↓
Interceptor.preHandle
      ↓
Controller
      ↓
Interceptor.postHandle
      ↓
View Render
      ↓
Interceptor.afterCompletion
      ↓
HTTP Response

拦截器是 Spring Web 开发中最重要的扩展机制之一,能够将大量通用逻辑从业务代码中抽离出来,实现:

  • 代码解耦
  • 统一控制
  • 统一监控
  • 统一安全策略

合理使用拦截器,可以显著提升系统架构的可维护性与扩展性。

正文到此结束
评论插件初始化中...
Loading...