SpringBoot拦截器详解:从基础到高级实战

一、拦截器基础概念

1.1 什么是拦截器?

拦截器(Interceptor)是Spring MVC框架提供的一种机制,允许你在请求处理的不同阶段进行拦截和处理。它类似于Servlet中的过滤器(Filter),但提供了更精细的控制和更丰富的功能。

通俗理解:想象你去银行办理业务,拦截器就像是银行大厅的各个服务环节:

  • 取号机:验证你是否是客户(预处理)
  • 大堂经理:检查你的材料是否齐全(预处理)
  • 柜台人员:处理你的核心业务(业务处理)
  • 评价器:询问你对服务的满意度(后处理)

1.2 拦截器 vs 过滤器

对比维度拦截器(Interceptor)过滤器(Filter)
归属Spring框架的一部分Servlet规范的一部分
依赖依赖于Spring容器不依赖任何框架
实现方式基于Java反射和动态代理基于函数回调
使用场景处理Spring相关的逻辑(如Controller调用)处理更底层的请求/响应
获取Bean可以直接获取Spring容器中的Bean无法直接获取Spring容器中的Bean
执行顺序在DispatcherServlet之后执行在DispatcherServlet之前执行
细粒度更细,可以精确到具体的Controller方法较粗,基于URL模式匹配
异常处理可以结合Spring的异常处理机制只能自行处理

1.3 拦截器的执行流程

客户端请求
    ↓
DispatcherServlet
    ↓
Interceptor.preHandle() → 返回false则流程终止
    ↓
Controller方法执行
    ↓
Interceptor.postHandle()
    ↓
视图渲染(如果有)
    ↓
Interceptor.afterCompletion()

二、拦截器核心方法与生命周期

2.1 拦截器的三个核心方法

方法名执行时机返回值典型应用场景
preHandleController方法执行前boolean权限验证、日志记录、参数预处理
postHandleController方法执行后,视图渲染前void修改模型数据、添加通用属性
afterCompletion整个请求完成后(视图渲染完毕)void资源清理、性能监控、异常记录

2.2 方法详细解析

2.2.1 preHandle 方法
/**
 * 在Controller方法执行前调用
 * @param request 当前HTTP请求
 * @param response 当前HTTP响应
 * @param handler 将被执行的处理器对象(通常是HandlerMethod)
 * @return 返回true则继续执行,返回false则中断流程
 * @throws Exception 可以抛出异常
 */
default boolean preHandle(HttpServletRequest request, 
                         HttpServletResponse response, 
                         Object handler) throws Exception {
    return true;
}

日常例子:就像进入公司大门需要刷卡,preHandle就是门禁系统:

  • 卡有效(preHandle返回true):允许进入
  • 卡无效(preHandle返回false):拒绝进入
2.2.2 postHandle 方法
/**
 * 在Controller方法执行后,视图渲染前调用
 * @param request 当前HTTP请求
 * @param response 当前HTTP响应
 * @param handler 被执行的处理器对象
 * @param modelAndView 控制器返回的ModelAndView(可能为null)
 * @throws Exception 可以抛出异常
 */
default void postHandle(HttpServletRequest request, 
                       HttpServletResponse response, 
                       Object handler, 
                       ModelAndView modelAndView) throws Exception {
}

日常例子:就像厨师做完菜后,服务员在上菜前对菜品进行最后的检查装饰。

2.2.3 afterCompletion 方法
/**
 * 在整个请求完成后调用(视图渲染完毕)
 * 通常用于资源清理
 * @param request 当前HTTP请求
 * @param response 当前HTTP响应
 * @param handler 被执行的处理器对象
 * @param ex 处理过程中抛出的异常(如果有)
 * @throws Exception 可以抛出异常
 */
default void afterCompletion(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler, 
                            Exception ex) throws Exception {
}

日常例子:就像餐厅客人离开后,服务员清理桌面准备迎接下一位客人。

2.3 多个拦截器的执行顺序

当配置多个拦截器时,它们的执行顺序如下:

  1. preHandle按配置顺序执行
  2. postHandle按配置逆序执行
  3. afterCompletion按配置逆序执行

记忆口诀:preHandle顺序喊"到",postHandle反着来,afterCompletion跟着postHandle走。

三、拦截器实战开发

3.1 创建自定义拦截器

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

/**
 * 日志拦截器:记录请求处理全过程的耗时
 */
public class LogInterceptor implements HandlerInterceptor {
    
    // 线程局部变量,用于记录开始时间
    private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        // 记录请求开始时间
        long startTime = System.currentTimeMillis();
        startTimeThreadLocal.set(startTime);
        
        // 打印请求信息
        System.out.println("[" + startTime + "] 请求开始: " + 
                          request.getRequestURI() + 
                          ", 参数: " + request.getQueryString());
        
        // 模拟权限检查
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":401,\"message\":\"未授权\"}");
            return false;
        }
        
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, 
                         HttpServletResponse response, 
                         Object handler, 
                         ModelAndView modelAndView) throws Exception {
        System.out.println("请求处理完成,准备渲染视图");
        
        // 可以在这里修改ModelAndView
        if (modelAndView != null) {
            modelAndView.addObject("interceptorMsg", "来自拦截器的消息");
        }
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                              HttpServletResponse response, 
                              Object handler, 
                              Exception ex) throws Exception {
        // 获取请求开始时间
        long startTime = startTimeThreadLocal.get();
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        
        // 打印耗时
        System.out.println("[" + endTime + "] 请求结束: " + 
                          request.getRequestURI() + 
                          ", 耗时: " + duration + "ms");
        
        // 清理线程局部变量
        startTimeThreadLocal.remove();
        
        // 如果有异常,可以在这里记录
        if (ex != null) {
            System.err.println("请求处理出现异常: " + ex.getMessage());
        }
    }
}

3.2 注册拦截器

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", "/error"); // 排除登录和错误页面
        
        // 可以注册多个拦截器
        // registry.addInterceptor(new AnotherInterceptor())...
        
        // 设置拦截器顺序(值越小优先级越高)
        // registry.addInterceptor(new FirstInterceptor()).order(1);
        // registry.addInterceptor(new SecondInterceptor()).order(2);
    }
}

3.3 测试Controller

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    
    @GetMapping("/hello")
    public String hello(@RequestHeader(value = "Authorization", required = false) String token) {
        System.out.println("Controller方法执行中...");
        System.out.println("Token: " + token);
        return "Hello, Spring Boot Interceptor!";
    }
    
    @GetMapping("/login")
    public String login() {
        return "Login Page";
    }
}

四、拦截器高级应用

4.1 权限验证拦截器

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

public class AuthInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        // 从请求头获取token
        String token = request.getHeader("Authorization");
        
        // 模拟验证token
        if (token == null || !token.equals("Bearer mock-token-123456")) {
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("{\"code\":401,\"message\":\"无效的Token\"}");
            return false;
        }
        
        // 验证通过,可以在这里设置用户信息到请求属性中
        request.setAttribute("currentUser", "user123");
        
        return true;
    }
}

4.2 接口限流拦截器

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.springframework.web.servlet.HandlerInterceptor;

public class RateLimitInterceptor implements HandlerInterceptor {
    
    // 使用ConcurrentHashMap存储每个IP的访问次数和时间
    private final ConcurrentHashMap<String, RateLimitRecord> rateLimitMap = new ConcurrentHashMap<>();
    
    // 限制配置:每秒最多5次请求
    private static final int MAX_REQUESTS = 5;
    private static final long TIME_WINDOW = 1000; // 1秒
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        String ip = request.getRemoteAddr();
        long currentTime = System.currentTimeMillis();
        
        // 获取或创建记录
        RateLimitRecord record = rateLimitMap.computeIfAbsent(
            ip, k -> new RateLimitRecord(currentTime, 0));
        
        // 如果时间窗口已过,重置计数
        if (currentTime - record.getTimestamp() > TIME_WINDOW) {
            record.setTimestamp(currentTime);
            record.setCount(0);
        }
        
        // 检查请求次数
        if (record.getCount() >= MAX_REQUESTS) {
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
            response.getWriter().write("{\"code\":429,\"message\":\"请求过于频繁,请稍后再试\"}");
            return false;
        }
        
        // 增加计数
        record.incrementCount();
        return true;
    }
    
    // 内部类,用于记录请求次数和时间戳
    private static class RateLimitRecord {
        private long timestamp;
        private int count;
        
        public RateLimitRecord(long timestamp, int count) {
            this.timestamp = timestamp;
            this.count = count;
        }
        
        // getter和setter方法
        public long getTimestamp() { return timestamp; }
        public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
        public int getCount() { return count; }
        public void setCount(int count) { this.count = count; }
        public void incrementCount() { this.count++; }
    }
}

4.3 数据脱敏拦截器

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

public class DataMaskingInterceptor implements HandlerInterceptor {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public void postHandle(HttpServletRequest request, 
                         HttpServletResponse response, 
                         Object handler, 
                         ModelAndView modelAndView) throws Exception {
        if (modelAndView != null && modelAndView.getModel() != null) {
            Map<String, Object> model = modelAndView.getModel();
            Map<String, Object> maskedModel = new HashMap<>();
            
            // 遍历模型数据,对敏感信息进行脱敏处理
            for (Map.Entry<String, Object> entry : model.entrySet()) {
                Object value = entry.getValue();
                if (value instanceof String) {
                    // 简单演示:对包含"phone"或"idCard"的字段进行脱敏
                    if (entry.getKey().toLowerCase().contains("phone")) {
                        maskedModel.put(entry.getKey(), maskPhone((String) value));
                    } else if (entry.getKey().toLowerCase().contains("idcard")) {
                        maskedModel.put(entry.getKey(), maskIdCard((String) value));
                    } else {
                        maskedModel.put(entry.getKey(), value);
                    }
                } else {
                    maskedModel.put(entry.getKey(), value);
                }
            }
            
            // 替换原始模型
            modelAndView.getModel().clear();
            modelAndView.getModel().putAll(maskedModel);
        }
    }
    
    private String maskPhone(String phone) {
        if (phone == null || phone.length() < 7) return phone;
        return phone.substring(0, 3) + "****" + phone.substring(7);
    }
    
    private String maskIdCard(String idCard) {
        if (idCard == null || idCard.length() < 15) return idCard;
        return idCard.substring(0, 6) + "********" + idCard.substring(14);
    }
}

五、拦截器进阶技巧

5.1 拦截器与异常处理

拦截器可以与Spring的异常处理机制结合使用:

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

public class ExceptionInterceptor implements HandlerInterceptor {
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response, 
                               Object handler, 
                               Exception ex) throws Exception {
        if (ex != null) {
            // 根据异常类型进行不同处理
            if (ex instanceof IllegalArgumentException) {
                response.setContentType("application/json;charset=UTF-8");
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                response.getWriter().write("{\"code\":400,\"message\":\"参数错误: " + ex.getMessage() + "\"}");
            }
            // 可以添加更多异常类型的处理...
        }
    }
}

5.2 拦截器与异步请求

对于异步请求(如使用@Async或WebAsyncTask),拦截器的行为有所不同:

  • preHandle: 在异步线程开始前执行
  • postHandle和afterCompletion: 默认不会执行

要让拦截器支持异步请求,需要实现AsyncHandlerInterceptor接口:

import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AsyncInterceptor implements AsyncHandlerInterceptor {
    
    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, 
                                            HttpServletResponse response, 
                                            Object handler) throws Exception {
        // 这个方法会在异步处理开始时调用
        System.out.println("异步请求开始处理: " + request.getRequestURI());
    }
    
    // 其他方法...
}

5.3 拦截器执行顺序控制

在Spring Boot中,可以通过实现Ordered接口或使用@Order注解来控制拦截器的执行顺序:

import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.HandlerInterceptor;

@Order(1) // 值越小优先级越高
public class FirstInterceptor implements HandlerInterceptor {
    // 实现方法...
}

@Order(2)
public class SecondInterceptor implements HandlerInterceptor {
    // 实现方法...
}

或者在注册时指定顺序:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new FirstInterceptor()).order(1);
    registry.addInterceptor(new SecondInterceptor()).order(2);
}

六、拦截器最佳实践

6.1 性能监控拦截器

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DecimalFormat;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.servlet.HandlerInterceptor;

public class PerformanceInterceptor implements HandlerInterceptor {
    
    private final AtomicInteger requestCount = new AtomicInteger(0);
    private final AtomicLong totalTime = new AtomicLong(0);
    private final ThreadLocal<Long> startTime = new ThreadLocal<>();
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        startTime.set(System.currentTimeMillis());
        requestCount.incrementAndGet();
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                              HttpServletResponse response, 
                              Object handler, 
                              Exception ex) throws Exception {
        long duration = System.currentTimeMillis() - startTime.get();
        totalTime.addAndGet(duration);
        startTime.remove();
        
        // 每100次请求打印一次性能统计
        if (requestCount.get() % 100 == 0) {
            double avgTime = (double) totalTime.get() / requestCount.get();
            DecimalFormat df = new DecimalFormat("#.##");
            
            System.out.println("\n===== 性能统计 =====");
            System.out.println("总请求数: " + requestCount.get());
            System.out.println("总处理时间: " + totalTime.get() + "ms");
            System.out.println("平均处理时间: " + df.format(avgTime) + "ms");
            System.out.println("当前请求[" + request.getRequestURI() + "]耗时: " + duration + "ms");
            System.out.println("==================\n");
        }
    }
}

6.2 API版本控制拦截器

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

public class ApiVersionInterceptor implements HandlerInterceptor {
    
    private static final String VERSION_HEADER = "X-API-Version";
    private static final String DEFAULT_VERSION = "1.0";
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        String version = request.getHeader(VERSION_HEADER);
        if (version == null) {
            version = DEFAULT_VERSION;
        }
        
        // 将版本信息设置到请求属性中,供Controller使用
        request.setAttribute("apiVersion", version);
        
        // 可以根据版本号做不同的处理逻辑
        if ("1.0".equals(version)) {
            // 旧版本逻辑
        } else if ("2.0".equals(version)) {
            // 新版本逻辑
        }
        
        return true;
    }
}

6.3 多租户拦截器

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

public class TenantInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        // 从请求头、Cookie或URL参数中获取租户ID
        String tenantId = request.getHeader("X-Tenant-ID");
        if (tenantId == null) {
            tenantId = request.getParameter("tenantId");
        }
        
        // 验证租户ID是否有效
        if (tenantId == null || tenantId.isEmpty()) {
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.getWriter().write("{\"code\":400,\"message\":\"缺少租户标识\"}");
            return false;
        }
        
        // 将租户信息存储到当前线程中(可以使用ThreadLocal)
        TenantContext.setCurrentTenant(tenantId);
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response, 
                               Object handler, 
                               Exception ex) throws Exception {
        // 清除线程中的租户信息
        TenantContext.clear();
    }
}

// 租户上下文类
class TenantContext {
    private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
    
    public static void setCurrentTenant(String tenant) {
        currentTenant.set(tenant);
    }
    
    public static String getCurrentTenant() {
        return currentTenant.get();
    }
    
    public static void clear() {
        currentTenant.remove();
    }
}

七、常见问题与解决方案

7.1 拦截器不生效的常见原因

  1. 拦截器未注册:忘记在配置类中调用addInterceptor方法
  2. 路径配置错误addPathPatternsexcludePathPatterns配置不正确
  3. 顺序问题:被其他拦截器提前终止
  4. Spring Boot版本问题:某些版本可能有不同的默认配置
  5. 拦截器Bean未正确初始化:如果拦截器中有依赖注入,确保它是Spring管理的Bean

7.2 拦截器中的依赖注入

如果需要在拦截器中使用Spring管理的Bean,可以将拦截器本身也声明为Bean:

@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    @Autowired
    private UserService userService;
    
    // 实现方法...
}

// 然后在配置类中注入拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private AuthInterceptor authInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor);
    }
}

7.3 拦截器与跨域(CORS)处理

拦截器和CORS处理器的执行顺序需要注意:

  1. 对于简单请求(Simple Request),CORS过滤器先执行
  2. 对于非简单请求,会先发送OPTIONS预检请求

如果拦截器拦截了OPTIONS请求,可能导致CORS失败。解决方案:

@Override
public boolean preHandle(HttpServletRequest request, 
                       HttpServletResponse response, 
                       Object handler) throws Exception {
    // 放行OPTIONS请求
    if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
        return true;
    }
    
    // 其他处理逻辑...
}

八、总结

8.1 拦截器核心要点总结

  1. 三个核心方法:preHandle、postHandle、afterCompletion
  2. 执行顺序:多个拦截器时,preHandle顺序执行,postHandle和afterCompletion逆序执行
  3. 应用场景:权限验证、日志记录、性能监控、数据脱敏等
  4. 与过滤器区别:拦截器更上层,能获取Spring上下文,执行时机不同
  5. 最佳实践:保持拦截器职责单一,注意性能影响,合理处理异常

关注不关注,你自己决定(但正确的决定只有一个)。

喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clf丶忆笙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值