Spring MVC拦截器案例

wen java案例 2

Spring MVC拦截器案例深度解析:从入门到实战(附完整代码)

目录导读

  1. 拦截器到底是什么?——核心概念与执行原理
  2. 第一个拦截器:5分钟快速上手(代码案例)
  3. 拦截器的“三兄弟”:preHandle / postHandle / afterCompletion 详解
  4. 多个拦截器的执行顺序与责任链模式
  5. 拦截器 vs 过滤器:别再傻傻分不清
  6. 企业级实战案例:登录鉴权 + 操作日志 + 性能监控
  7. 常见坑点与最佳实践(高频面试题问答)

拦截器到底是什么?——核心概念与执行原理

Spring MVC的拦截器(Interceptor) 是一种面向切面编程(AOP)的轻量级实现,它允许你在处理器(Controller)方法执行前后插入自定义逻辑,而无需修改原有代码,它的核心是基于HandlerExecutionChain链式调用,由DispatcherServlet统一调度。

Spring MVC拦截器案例

执行原理流程图(简化版)

请求 → DispatcherServlet → 找到HandlerMapping → 返回HandlerExecutionChain
     → 依次执行所有拦截器的preHandle()
     → 若全部通过,则调用Controller方法
     → 依次执行postHandle()(逆序)
     → 视图渲染
     → 依次执行afterCompletion()(逆序)

与核心注解的关系:拦截器作用于HandlerMapping之后,因此它能看到请求对应的Handler(Controller方法),但看不到视图渲染之后的结果。


第一个拦截器:5分钟快速上手(代码案例)

Step 1:实现HandlerInterceptor接口

public class FirstInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        System.out.println("[First] 请求前执行,可以进行权限校验");
        return true; // 放行
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, 
            ModelAndView modelAndView) throws Exception {
        System.out.println("[First] 请求后视图前执行,可以修改ModelAndView");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, 
            Exception ex) throws Exception {
        System.out.println("[First] 视图渲染完成后执行,可以释放资源/记录日志");
    }
}

Step 2:注册拦截器(Spring配置类)

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new FirstInterceptor())
                .addPathPatterns("/**")       // 拦截所有路径
                .excludePathPatterns("/login", "/static/**"); // 放行登录页和静态资源
    }
}

Step 3:测试 启动应用后访问任意Controller路径,控制台会按顺序打印三个方法中的LOG。


拦截器的“三兄弟”:三大核心方法深度对比

方法 执行时机 返回值 典型用途
preHandle Controller方法执行前 boolean(true=放行,false=拦截) 登录校验、权限检查、黑名单过滤
postHandle Controller方法执行后、视图渲染前 void 向ModelAndView注入公共数据、敏感词过滤
afterCompletion 视图渲染完成后(无论是否抛异常) void 日志记录、资源清理、性能统计

关键细节

  • 如果preHandle返回false,后续该方法所在的整个链路(包括postHandle、afterCompletion和Controller方法)不会执行,但已执行的preHandle仍然会触发对应的afterCompletion(逆序)。
  • postHandle只有在该拦截器自身preHandle返回true后才会被调用。

多个拦截器的执行顺序与责任链模式

执行规则:多个拦截器按注册顺序形成一个责任链,执行顺序为:

请求 → Interceptor1.preHandle → Interceptor2.preHandle → Controller
     → Interceptor2.postHandle → Interceptor1.postHandle → 视图渲染
     → Interceptor2.afterCompletion → Interceptor1.afterCompletion

代码验证

registry.addInterceptor(new InterceptorA()).addPathPatterns("/**");
registry.addInterceptor(new InterceptorB()).addPathPatterns("/**");

输出顺序:A.pre → B.pre → Controller → B.post → A.post → B.after → A.after

重要结论

  • preHandle注册顺序执行
  • postHandleafterCompletion注册逆序执行(类似栈的弹出)

拦截器 vs 过滤器:别再傻傻分不清

对比维度 Filter(过滤器) Interceptor(拦截器)
所属规范 Servlet规范(服务端通用) Spring框架自带(仅适用于Spring项目)
作用范围 所有请求(包括静态资源) 仅作用于HandlerMapping匹配的处理器
依赖容器 依赖Servlet容器(如Tomcat) 依赖Spring IOC容器,可获取Bean
访问能力 无法直接访问Spring管理的Bean 可以直接注入Service、DAO
执行时机 进入Servlet之前 进入DispatcherServlet之后
获取Handler 只能拿到Request/Response 可以获取目标Handler(Controller方法)
典型场景 编码转换、CORS跨域、请求日志 权限校验、敏感词过滤、数据注入

适用选择:如果功能与Spring无关(如字符编码),用Filter;如果涉及Spring Bean或Controller信息,用Interceptor。


企业级实战案例:登录鉴权 + 操作日志 + 性能监控

案例场景

/admin/**路径进行登录拦截,记录每个接口调用耗时,并写入操作日志。

完整代码

@Component
public class AdminAuthInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService;  // 注入业务服务
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        // 1. 判断是否登录(从Session或Token获取)
        Object user = request.getSession().getAttribute("LOGIN_USER");
        if (user == null) {
            // 未登录,重定向到登录页
            response.sendRedirect("/login");
            return false;
        }
        // 2. 判断路径权限(假设:所有/admin/delete需要管理员权限)
        String uri = request.getRequestURI();
        if (uri.contains("/delete") && !userService.isAdmin((User) user)) {
            response.sendError(403, "无权限操作");
            return false;
        }
        // 3. 将用户信息放入请求域,供Controller使用
        request.setAttribute("currentUser", user);
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, 
            ModelAndView modelAndView) throws Exception {
        // 向所有Admin页面注入公共数据(如菜单列表)
        if (modelAndView != null) {
            modelAndView.addObject("menu", menuService.getAdminMenus());
        }
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, 
            Exception ex) throws Exception {
        // 记录访问日志(包括耗时)
        long startTime = (Long) request.getAttribute("startTime");
        long costTime = System.currentTimeMillis() - startTime;
        logService.writeLog(request.getRequestURI(), costTime, request.getRemoteAddr());
        // 资源清理
        System.out.println("接口耗时:" + costTime + "ms");
    }
}

注册配置增强

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new BaseInterceptor())  // 公共拦截:获取起始时间
            .addPathPatterns("/**");
    registry.addInterceptor(adminAuthInterceptor)   // 管理端鉴权
            .addPathPatterns("/admin/**")
            .excludePathPatterns("/admin/login", "/admin/logout");
}

常见坑点与最佳实践(高频面试题问答)

Q1:preHandle返回false后,afterCompletion还会执行吗?

分为两种情况:如果当前拦截器的preHandle返回false,则当前拦截器的afterCompletion不会执行;但之前已执行过preHandle并返回true的拦截器的afterCompletion会逆序执行,例:A.pre=true → B.pre=false → 只会执行A.after。

Q2:拦截器如何获取Controller方法的参数?

通过HandlerMethod对象:HandlerMethod hm = (HandlerMethod) handler; 然后使用hm.getMethodParameters()获取参数列表,或hm.getMethod()获取注解。

Q3:拦截器能拦截异步请求(DeferredResult)吗?

默认情况下,拦截器只拦截第一次请求,对于异步处理,Spring 3.2+提供了AsyncHandlerInterceptor接口,其中afterConcurrentHandlingStarted方法在异步请求开始时触发。

Q4:如何让拦截器不拦截静态资源?

在注册时使用excludePathPatterns("/static/**", "/css/**", "/js/**", "/images/**"),或者更优雅的方式是使用resourceHandler配置。

最佳实践总结

  1. 善用排除路径:登录页、静态资源、开放API一定不要拦截
  2. 获取Bean:拦截器要注入Service时,建议使用@Component注解而不是new,或在配置中手动注入
  3. 异常处理:拦截器中的异常如果未捕获,会中断请求处理,建议在afterCompletion中统一处理
  4. 性能考虑:拦截器调用的外部服务要加超时控制,避免阻塞
  5. 测试策略:单元测试时注意mock拦截器,集成测试时通过MockMvc验证拦截行为

Spring MVC拦截器是构建健壮Web应用的利器,它让你在不侵入业务代码的前提下,优雅地实现横切关注点,从简单的日志打印到复杂的权限体系,掌握好这"三兄弟"加上责任链规则,足以应对绝大多数企业级需求,建议你亲手部署一个含有登录鉴权+性能监控的Demo,彻底吃透它的生命周期。

抱歉,评论功能暂时关闭!