Spring MVC 里那几个“长得像”的东西,到底该用哪个
起因
写接口的时候想加点统一逻辑,比如记日志、校验登录、包装返回体。搜一圈发现能用的东西不少:Filter、Interceptor、AOP、@ControllerAdvice。每个教程都说自己能干,但真写进去发现有的拿不到 Bean,有的拦不到 Service,有的对静态资源也生效。
这篇把几个东西的边界理一下,顺便记一下我选型时的判断标准。
先看它们在哪一层
按请求进来的顺序,大致是这样的:
浏览器 → Filter → DispatcherServlet → Interceptor → Controller 方法(AOP 可以套在 Controller 或 Service 上)→ 返回 → Interceptor 后处理 → Filter 后处理。
@ControllerAdvice 比较特殊,它不是一个拦截器,而是对 Controller 层的增强,主要用来做全局异常处理和返回体包装。
Filter:容器层面的门卫
Filter 属于 Servlet 规范,不依赖 Spring,所以它拿不到 Spring 容器里的 Bean(除非用一些绕的办法)。
适合干的事:编码设置、CORS 预检放行、原始请求日志、IP 黑名单。这些操作只需要 HttpServletRequest 和 HttpServletResponse,不需要知道后面哪个 Controller 处理。
我之前用 Filter 做过接口耗时统计,能跑,但后来换了 Interceptor,因为 Filter 里拿不到 Handler 信息,不知道具体打到了哪个方法,日志里只有 URL,排查的时候不够用。
Interceptor:Spring MVC 内部的关卡
Interceptor 是 Spring MVC 的组件,能注入 Bean,也能拿到即将执行的 Handler 信息。preHandle 里可以判断登录、权限,postHandle 里能改 Model,afterCompletion 做资源清理。
一个实际场景:需要根据当前用户角色判断有没有权限访问某个接口。用 Interceptor 的 preHandle,注入 UserService 查权限,拿到 handler 参数能知道目标方法上有没有自定义注解,组合判断后决定放不放行。这件事 Filter 做不了,因为 Filter 阶段还不知道请求最终会落到哪个方法。
注意一点:Interceptor 默认不拦静态资源,但如果注册时路径写成了 /**,静态资源请求也会进来,preHandle 里可能报空指针。要么路径写精确些,要么在 preHandle 里判断是不是 HandlerMethod,不是就直接放行。
@ControllerAdvice:只管 Controller 层的那些事
它的定位很明确:全局异常处理、全局数据绑定、全局返回体增强。
我项目里用 @RestControllerAdvice 配合 @ExceptionHandler 统一处理业务异常,返回固定的 JSON 结构。这个用 Interceptor 做不了,因为 Interceptor 的 postHandle 拿不到方法抛出的异常(异常会走 afterCompletion,但那时候 response 可能已经开始写了)。
另一个常用的是 ResponseBodyAdvice,实现 beforeBodyWrite 可以在所有 @ResponseBody 方法返回之前统一包装 Result 对象。前提是类上得加 @ControllerAdvice,只加 @Component 不生效,因为 Spring 扫描的是 @ControllerAdvice 注解的 Bean。
它管不到 Service 层。Service 抛的异常如果没传到 Controller,@ExceptionHandler 也捕获不到(除非异常一路抛到了 DispatcherServlet)。
AOP:方法级别的,和 HTTP 没关系
AOP 切的是方法调用,不管这个方法是被 HTTP 请求触发的,还是被定时任务、MQ 消费者调用的。
所以如果有一段逻辑需要同时作用于 Controller 和 Service,AOP 是唯一的选择。比如操作日志记录,我在 Service 方法上切,不管从哪个入口进来的调用都能记到。
但 AOP 拿不到原始的 HttpServletRequest(除非用 RequestContextHolder 绕)。所以做登录校验、参数预处理这类需要请求上下文的事情,还是 Interceptor 更顺手。
我现在的选型习惯
需要操作 HttpServletRequest/Response、处理跨域、编码:Filter。
需要判断登录态、权限、访问日志(要记录具体方法):Interceptor。
统一异常返回、统一包装 Result:@ControllerAdvice。
业务方法的操作日志、耗时统计,且可能被非 HTTP 入口调用:AOP。
拿不准的时候,先看“这个逻辑需不需要知道 HTTP 请求”。需要,往 Filter/Interceptor 靠;不需要,AOP 更通用。再看“需不需要知道是哪个 Controller 方法”。需要,Interceptor;不需要,Filter。
一个小坑
有一次在 Interceptor 的 preHandle 里抛了自定义异常,以为 @RestControllerAdvice 能接住,结果没有。原因是 preHandle 执行时还没进入 HandlerAdapter 的调用流程,异常处理器的链路还没准备好。Interceptor 里的异常要么自己处理,要么写到 response 里返回,别指望全局异常处理器兜底。
先记这些。这几个东西名字听着像,用起来差别挺大的,踩过一次就记住了。
- 点赞
- 收藏
- 关注作者
评论(0)