Spring Boot拦截器详解与项目实战

一、拦截器概述与核心概念

Spring Boot拦截器(Interceptor)是基于Spring MVC框架的一种请求拦截机制,它允许开发者在请求处理的不同阶段插入自定义逻辑。拦截器主要作用于Controller层,能够在请求到达Controller之前、Controller方法执行之后以及整个请求完成之后执行特定操作。

拦截器与过滤器的区别主要体现在:

  • 所属框架:拦截器属于Spring MVC,而过滤器是Servlet规范的一部分
  • 作用范围:拦截器仅作用于Controller方法请求,过滤器作用于所有请求(包括静态资源)
  • 依赖注入:拦截器支持Spring依赖注入,过滤器不支持
  • 执行时机:过滤器先于拦截器执行,拦截器后于过滤器执行

拦截器的典型应用场景包括:

  • 登录验证与权限控制
  • 请求日志记录
  • 性能监控
  • 防止重复提交
  • 统一设置响应头信息

二、拦截器核心方法与执行流程

1. 拦截器三个核心方法

拦截器通过实现HandlerInterceptor接口并重写其三个核心方法:

java 复制代码
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;
}

preHandle方法​:

  • 在Controller方法执行前调用
  • 返回true表示继续执行后续拦截器和目标方法,返回false则中断执行
  • 常用于权限校验、日志记录等

postHandle方法​:

  • 在Controller方法执行后、视图渲染前调用
  • 可修改ModelAndView对象
  • 使用频率较低

afterCompletion方法​:

  • 在整个请求完成后调用(视图渲染后)
  • 常用于资源清理、日志记录等

2. 拦截器执行流程

拦截器的完整执行顺序如下:

  1. 请求到达DispatcherServlet
  2. 拦截器1.preHandle() → 拦截器2.preHandle() → ... → 拦截器N.preHandle()
  3. 目标Controller方法执行
  4. 拦截器N.postHandle() ← ... ← 拦截器2.postHandle() ← 拦截器1.postHandle()
  5. 视图渲染
  6. 拦截器1.afterCompletion() ← ... ← 拦截器N.afterCompletion()

如果任一拦截器的preHandle方法返回false,则后续拦截器和目标方法都不会执行,但已执行的拦截器的afterCompletion方法仍会被调用。

三、拦截器实战配置

1. 创建自定义拦截器

创建一个实现HandlerInterceptor接口的拦截器类:

java 复制代码
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AuthInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 登录校验逻辑
        String token = request.getHeader("Authorization");
        if(token == null || !token.equals("valid_token")) {
            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 {
        // 可添加响应头等操作
        response.setHeader("X-Custom-Header", "interceptor-added");
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 资源清理或日志记录
        System.out.println("请求完成: " + request.getRequestURI());
    }
}

2. 注册拦截器

通过实现WebMvcConfigurer接口并重写addInterceptors方法来注册拦截器:

typescript 复制代码
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 AuthInterceptor())
                .addPathPatterns("/api/**") // 拦截路径
                .excludePathPatterns("/api/login", "/api/register"); // 排除路径
    }
}

3. 拦截规则配置

  • addPathPatterns():指定需要拦截的URL模式,支持Ant风格路径匹配
  • excludePathPatterns():指定不需要拦截的URL模式
  • 多个拦截器可通过order()方法设置执行顺序,数值越小优先级越高

四、项目实战案例

案例1:登录状态校验

java 复制代码
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("userinfo") != null) {
            return true; // 已登录,放行
        }
        // 未登录,重定向到登录页
        response.sendRedirect("/user/login");
        return false;
    }
}

注册配置:

typescript 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/user/login", "/user/reg", "/**/*.css", "/**/*.jpg");
    }
}

案例2:请求日志记录与性能监控

java 复制代码
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 {
        startTimeThreadLocal.set(System.currentTimeMillis());
        System.out.println("【请求开始】IP: " + request.getRemoteAddr() + 
                         ", URI: " + request.getRequestURI());
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                              Object handler, Exception ex) throws Exception {
        long duration = System.currentTimeMillis() - startTimeThreadLocal.get();
        System.out.println("【请求结束】耗时: " + duration + "ms");
        startTimeThreadLocal.remove();
    }
}

案例3:防止重复提交

java 复制代码
public class RepeatSubmitInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getParameter("formToken");
        if (StringUtils.isEmpty(token) || RedisUtil.exists(token)) {
            response.getWriter().write("{"code":400,"message":"请勿重复提交"}");
            return false;
        }
        RedisUtil.set(token, "submitted", 60); // 设置60秒有效期
        return true;
    }
}

五、高级应用与最佳实践

1. 拦截器与AOP结合

对于更复杂的场景,可以结合拦截器和AOP实现更强大的功能:

less 复制代码
@Aspect
@Component
public class ApiLogAspect {
    @Around("@annotation(apiLog)")
    public Object around(ProceedingJoinPoint pjp, ApiLog apiLog) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        long cost = System.currentTimeMillis() - start;
        // 记录日志或执行其他操作
        return result;
    }
}

拦截器负责请求/响应包装,AOP负责业务逻辑处理,两者互补。

2. 静态资源放行

为避免拦截器影响静态资源访问,需配置排除路径:

typescript 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/", "/login", "/static/**", "/public/**");
    }
}

3. 性能优化建议

  • 避免在拦截器中执行耗时操作(如数据库查询、远程调用)
  • 对于需要异步处理的操作,使用线程池
  • 使用ThreadLocal时注意及时清理,防止内存泄漏

六、常见问题与解决方案

  1. 拦截器不生效

    • 检查是否添加了@Configuration注解
    • 确认拦截路径配置正确
    • 确保拦截器类被Spring管理(如添加@Component注解)
  2. 静态资源被拦截

    • 使用excludePathPatterns()排除静态资源路径
  3. 多个拦截器执行顺序问题

    • 通过order()方法明确指定执行顺序
  4. 获取请求Body内容

    • 使用ContentCachingRequestWrapper包装请求,解决流只能读取一次的问题

七、总结

Spring Boot拦截器是Web开发中强大的工具,通过合理使用可以:

  • 集中处理公共逻辑,减少代码冗余
  • 提高代码可维护性和可读性
  • 实现安全控制、日志记录等横切关注点

在实际项目中,应根据具体需求选择合适的拦截策略,并注意性能优化和资源管理。结合AOP等其他技术,可以构建更加灵活、强大的请求处理管道。

通过本文的讲解和实战案例,开发者应能够掌握Spring Boot拦截器的核心概念、使用方法和最佳实践,从而在实际项目中灵活应用。

相关推荐
devlei5 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
努力的小郑6 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3567 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3567 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁7 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp7 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴9 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友9 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒10 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端
Soofjan11 小时前
Go 内存回收-GC 源码1-触发与阶段
后端