Spring Boot 拦截器使用全指南

1. Spring Boot 拦截器概念与核心思想

1.1 拦截器是什么

拦截器(Interceptor)是 Spring 框架提供的一种处理机制,用于在请求到达 Controller 之前或之后执行特定逻辑。它类似于 Servlet Filter,但功能更强大,能够获取 Spring MVC 的上下文,并操作请求和响应对象。

拦截器的核心特点:

  • 链式调用:多个拦截器按顺序执行,前一个拦截器决定是否调用下一个。
  • 面向请求处理:拦截器主要作用于 Controller 层,处理请求和响应。
  • 灵活配置:可根据 URL、请求方法或条件配置拦截路径。

1.2 为什么需要拦截器

拦截器解决了很多通用需求,例如:

  • 身份认证与权限校验:请求到达 Controller 前验证用户身份。
  • 日志记录:记录访问日志、操作日志。
  • 性能监控:统计请求耗时。
  • 请求统一处理:如添加通用响应头、设置请求属性等。

1.3 拦截器与过滤器区别

特性 拦截器 (Interceptor) 过滤器 (Filter)
生命周期 Spring 容器管理 Servlet 容器管理
功能范围 Controller 层 所有请求,包括静态资源
可以访问 Spring MVC 上下文、Handler 原生 HttpServletRequest、HttpServletResponse
配置方式 WebMvcConfigurer 添加 web.xml 或 @WebFilter 注解

作者经验总结:拦截器与过滤器各有适用场景,拦截器更适合与 Spring MVC 深度集成,推荐用于业务层统一处理逻辑,过滤器适合全局请求控制或跨框架拦截。


2. 拦截器工作原理与执行流程

2.1 核心执行流程

Spring MVC 拦截器主要依赖 HandlerInterceptor 接口,执行流程如下:

客户端请求 → DispatcherServlet → HandlerMapping → HandlerInterceptor.preHandle
      → Controller → HandlerInterceptor.postHandle
      → ViewResolver → DispatcherServlet → HandlerInterceptor.afterCompletion → 响应客户端

三个关键方法:

  1. preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

    • 在 Controller 方法执行前调用
    • 返回 true 表示继续执行,返回 false 阻止请求继续处理
  2. postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)

    • Controller 方法执行后,渲染视图前调用
    • 可修改 ModelAndView,添加通用数据
  3. afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)

    • 请求处理完成后调用(无论是否异常)
    • 常用于清理资源、日志记录、性能统计

2.2 图示思维

┌─────────────┐
│ Client      │
└───────┬─────┘
        │
        ▼
┌─────────────┐
│ Dispatcher  │
│ Servlet     │
└───────┬─────┘
        │
        ▼
┌────────────────────┐
│ HandlerInterceptor │
│   preHandle        │
└────────┬───────────┘
         │ true
         ▼
┌─────────────┐
│ Controller  │
└───────┬─────┘
        │
        ▼
┌────────────────────┐
│ HandlerInterceptor │
│   postHandle       │
└────────┬───────────┘
         ▼
┌─────────────┐
│ View        │
└───────┬─────┘
        ▼
┌────────────────────┐
│ HandlerInterceptor │
│ afterCompletion    │
└────────────────────┘
        ▼
      Response

作者经验总结:理解拦截器的执行顺序是关键,特别是 preHandle 决定请求是否继续,postHandle 可修改返回结果,afterCompletion 用于收尾工作。


3. Spring Boot 拦截器配置与实现

3.1 创建自定义拦截器

实现 HandlerInterceptor 接口或继承 HandlerInterceptorAdapter(Spring 5 已废弃 HandlerInterceptorAdapter,推荐直接实现接口)。

package com.example.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("请求开始:" + request.getRequestURI());
        return true; // 继续执行请求
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("请求处理完成,渲染视图前执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println("请求完成,清理资源");
    }
}

3.2 注册拦截器

在 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 WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .addPathPatterns("/**") // 拦截所有请求
                .excludePathPatterns("/login", "/static/**"); // 排除部分请求
    }
}

3.3 注意事项

  1. 顺序问题:多个拦截器按注册顺序执行。
  2. 静态资源:需排除 /static/**,否则 CSS、JS、图片请求也会被拦截。
  3. 返回值控制preHandle 返回 false 会直接结束请求。

作者经验总结:在实际项目中,拦截器注册要分清通用拦截器与特定业务拦截器,避免影响静态资源和外部接口。


4. 实战案例:统一登录认证拦截器

4.1 需求分析

  • 所有请求需要登录验证
  • 未登录用户返回 JSON 提示
  • 登录状态保存在 Session 或 Redis

4.2 实现思路

  1. preHandle 检查请求头或 Session 中的登录状态
  2. 未登录返回 JSON
  3. 登录则继续执行 Controller

4.3 代码示例

package com.example.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;

import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

public class AuthInterceptor implements HandlerInterceptor {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object user = request.getSession().getAttribute("user");
        if (user == null) {
            response.setContentType("application/json;charset=UTF-8");
            Map<String, Object> result = new HashMap<>();
            result.put("code", 401);
            result.put("msg", "未登录");
            PrintWriter writer = response.getWriter();
            writer.write(objectMapper.writeValueAsString(result));
            writer.flush();
            return false;
        }
        return true;
    }
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login", "/register", "/static/**");
    }
}

作者经验总结:统一认证拦截器可以极大减少 Controller 中重复代码,但要注意排除登录、注册等公共接口,避免循环拦截。


5. 高级用法与扩展

5.1 拦截器传递参数

可以通过 request.setAttributepreHandle 设置数据,Controller 或 postHandle 可直接读取。

request.setAttribute("startTime", System.currentTimeMillis());

afterCompletion 统计耗时:

long start = (Long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - start;
System.out.println("请求耗时:" + duration + "ms");

5.2 异步请求拦截器

对于异步请求(@AsyncCallable),preHandle 依旧先执行,但 postHandle 可能延迟,需要结合 DeferredResultCompletableFuture 处理。

5.3 全局异常与拦截器结合

拦截器的 afterCompletion 方法可捕获异常对象 ex,用于统一日志和监控:

if (ex != null) {
    System.err.println("请求异常:" + ex.getMessage());
}

作者经验总结:拦截器不仅能做权限控制,也适合做全局日志、性能监控和异常收集,结合 AOP 可实现更精细化业务逻辑。


6. 常见错误与排查方法

错误场景 可能原因 解决方案
拦截器未生效 未注册到 WebMvcConfigurer 检查配置类是否加 @Configuration,addInterceptors 是否调用
静态资源被拦截 addPathPatterns 拦截了 /static/** 使用 excludePathPatterns 排除静态资源路径
preHandle 返回 false,但 Controller 仍执行 返回 false 前未正确中断流 确认 preHandle 中 return false 后没有调用 response.sendRedirect 或继续链
顺序不符合预期 多个拦截器注册顺序 调整 registry.addInterceptor 顺序
异步请求日志异常 postHandle 不适用异步 使用 afterCompletion 或结合 AsyncListener

作者经验总结:排查拦截器问题的核心是确认注册顺序、排除路径、返回值逻辑,以及异步场景下方法调用顺序。


7. 实践案例:访问日志与性能监控拦截器

7.1 需求分析

  • 记录请求 URL、方法、用户信息
  • 统计接口耗时
  • 输出日志到控制台或日志系统

7.2 代码示例

package com.example.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;

public class RequestLogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        long start = System.currentTimeMillis();
        request.setAttribute("startTime", 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("startTime");
        long duration = System.currentTimeMillis() - start;
        System.out.println("请求完成:" + request.getMethod() + " " + request.getRequestURI() + " 耗时:" + duration + "ms");
        if (ex != null) {
            System.err.println("请求异常:" + ex.getMessage());
        }
    }
}
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestLogInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/static/**");
    }
}

作者经验总结:这种拦截器适用于微服务监控、接口分析,可结合 ELK 或 Prometheus 收集数据,实现实时监控和告警。


8. 最佳实践总结

  1. 明确拦截器职责:每个拦截器只做一件事,如日志、权限、性能统计,避免过度耦合。
  2. 排除静态资源与公共接口:防止影响页面加载和外部 API。
  3. 尽量轻量preHandlepostHandle 尽量快速,避免阻塞请求。
  4. 结合 AOP:复杂业务逻辑或异常处理可以通过 AOP 与拦截器结合。
  5. 日志规范化:输出可追踪的请求 ID,便于问题排查。
  6. 顺序控制:注册顺序决定执行顺序,注意链式逻辑。

9. 延伸阅读

  • Spring MVC 官方文档关于 HandlerInterceptor 的说明
  • Spring Boot WebMvcConfigurer 拦截器注册技巧
  • 异步请求与拦截器结合的最佳实践
  • 与 AOP、Filter、Aspect 对比使用场景
  • 性能优化与日志分析实践

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