Spring MVC拦截器异常处理与跨域详解

Spring MVC拦截器异常处理与跨域详解

定位:第 04 篇,讲透拦截器三回调与过滤器对比、全局异常处理体系、CORS 跨域原理与配置

适用版本:Spring Framework 6.x(JDK 17+)


目录


一、拦截器体系

1.1 三个回调

java 复制代码
public class AuthInterceptor implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest req, ...) {
        // 执行前:鉴权检查;返回 false 则中断,不再执行后续与 Handler
        return true;
    }

    public void postHandle(...) {
        // Handler 执行后、视图渲染前:可修改 Model
    }

    public void afterCompletion(...) {
        // 完成后(含异常):清理资源(如解绑 ThreadLocal)
    }
}

执行时序(洋葱模型):

复制代码
preHandle → Handler → postHandle → 视图/响应 → afterCompletion
preHandle 返回 false:后续拦截器与 Handler 都不执行

1.2 注册与顺序

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    public void addInterceptors(InterceptorRegistry reg) {
        reg.addInterceptor(new AuthInterceptor())
           .addPathPatterns("/api/**")        // 拦截路径
           .excludePathPatterns("/api/login") // 排除
           .order(1);                          // 多个拦截器的顺序
    }
}

1.3 拦截器与过滤器的对比(高频)

维度 Filter(过滤器) HandlerInterceptor(拦截器)
所属层 Servlet 规范(容器层) Spring MVC(框架层)
位置 在 DispatcherServlet 之外 在 DispatcherServlet 之内
能拿到 原始请求/响应 还能拿到 Handler/方法信息
典型用途 编码、鉴权(粗)、压缩、跨域 登录校验、权限、日志、埋点
生效范围 所有请求(含静态) 进入 MVC 处理的请求

记忆:过滤器在外圈管"请求",拦截器在内圈管"处理器"。


二、全局异常处理

2.1 异常解析链

DispatcherServlet 捕获到 Handler 抛出的异常后,交给 HandlerExceptionResolver 链:

复制代码
① ExceptionHandlerExceptionResolver   → 找 @ExceptionHandler(方法级/全局)
② ResponseStatusExceptionResolver     → @ResponseStatus 注解
③ DefaultHandlerExceptionResolver     → 标准 Spring 异常映射(404/405 等)
都处理不了 → 转发 /error(Boot)

2.2 @RestControllerAdvice 全局处理

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)   // 校验
    ResponseEntity<ApiError> invalid(MethodArgumentNotValidException e) {
        return badRequest(字段级错误明细);
    }

    @ExceptionHandler(BizException.class)                       // 业务
    ResponseEntity<ApiError> biz(BizException e) {
        return status(e.httpStatus()).body(ApiError.of(e.getCode(), e.getMessage()));
    }

    @ExceptionHandler(Exception.class)                          // 兜底
    ResponseEntity<ApiError> fallback(Exception e) {
        log.error("unhandled", e);   // 记录,不外泄堆栈
        return internal(ApiError.of("SYSTEM_ERROR", "系统繁忙"));
    }
}

2.3 分层处理策略

异常类别 状态码 响应内容 是否告警
参数校验 400 字段级明细 否
业务异常 400/409 等 业务错误码 + 消息 视情况
认证/授权 401/403 标准消息 否/监控
未知异常 500 通用消息(不回传堆栈) 是,告警

纪律:异常分类处理、堆栈只进日志不进响应、未知异常必须告警。


三、CORS 跨域

3.1 同源策略

浏览器的安全约束:脚本只能请求同源 (协议 + 域名 + 端口一致)的资源;跨源请求默认被拦截。注意:这是浏览器的行为,服务端间调用不存在跨域问题。

3.2 CORS 机制

CORS 是服务端通过响应头声明"允许哪些跨源访问":

复制代码
简单请求(GET/部分 POST 等):直接发,靠响应头判定
非简单请求:先发预检(OPTIONS)询问 → 服务端允许后才发真实请求

关键头:

头 含义
Access-Control-Allow-Origin 允许的源(* 或具体域名)
Access-Control-Allow-Methods 允许的方法
Access-Control-Allow-Headers 允许的自定义头
Access-Control-Allow-Credentials 是否允许带 Cookie

3.3 Spring 的三种配置

复制代码
① 注解:@CrossOrigin(origins = "https://web.example.com")  // 单接口
② 全局:WebMvcConfigurer#addCorsMappings
        registry.addMapping("/api/**")
                .allowedOrigins("https://web.example.com")
                .allowedMethods("*");
③ CorsFilter(过滤器,位置最外,适合与安全框架共存)

注意:Allow-Credentials=true 时 Allow-Origin 不能是 *,必须指定具体域名。


四、总结

  1. 拦截器:preHandle(可中断)/postHandle/afterCompletion 三回调;注册指定路径与顺序;与过滤器分层不同------过滤器在 Servlet 外圈,拦截器在 MVC 内圈。
  2. 异常处理:HandlerExceptionResolver 链;@RestControllerAdvice 全局按类别转统一错误响应;未知异常记日志告警、不外泄堆栈。
  3. CORS :浏览器同源策略的例外机制,靠响应头声明;简单请求直发、非简单先预检(OPTIONS);配置三种方式,带凭证时不能 *。

五、常见高频面试题

1. HandlerInterceptor 的三个方法分别在什么时候执行?

要点:preHandle 在 Handler 执行前调用,返回 false 则中断(后续拦截器与 Handler 都不执行),常用于鉴权;postHandle 在 Handler 执行后、视图渲染前,可修改 Model;afterCompletion 在请求完全完成后(含异常),用于清理资源(如解绑 ThreadLocal)。多个拦截器按注册顺序形成链,整体是洋葱模型。

2. 过滤器(Filter)和拦截器(Interceptor)的区别?

要点:所属层与位置不同。Filter 是 Servlet 规范,在容器层、DispatcherServlet 之外,对所有请求生效(含静态资源),只能拿到原始请求响应,常用于编码、压缩、粗粒度鉴权、跨域;HandlerInterceptor 是 Spring MVC 层,在 DispatcherServlet 之内,能拿到 Handler/方法信息,作用于进入 MVC 的请求,常用于登录校验、权限、埋点。简记:过滤器在外圈管请求,拦截器在内圈管处理器。

3. Spring MVC 的异常处理机制是怎样的?

要点:Handler 抛出的异常被 DispatcherServlet 交给 HandlerExceptionResolver 链:先找 @ExceptionHandler(方法级,再到 @ControllerAdvice 全局),再看 @ResponseStatus,最后 DefaultHandlerExceptionResolver 映射标准异常(如 NoHandlerFound→404);都处理不了转发 /error。实践用 @RestControllerAdvice 统一处理,按异常类型返回统一错误响应。

4. @ControllerAdvice 的原理和用途?

要点:@ControllerAdvice 是一个特殊的 @Component,通过 AOP 横切所有 @Controller,提供全局的异常处理(@ExceptionHandler)、数据绑定(@ModelAttribute)、数据预处理(@InitBinder)。配合 @ResponseBody 的 @RestControllerAdvice 是全局异常处理的标准实现,把各类异常统一转为规范错误响应,避免每个控制器重复写异常逻辑。

5. 什么是 CORS?为什么会有跨域问题?

要点:浏览器同源策略限制脚本跨源(协议+域名+端口不同)访问资源,是安全机制。跨源 XHR/fetch 默认被拦截。CORS 让服务端用响应头声明允许的跨源访问:Access-Control-Allow-Origin 等。注意跨域是浏览器行为,服务端间调用无此问题。非简单请求会先发 OPTIONS 预检,服务端放行后才发真实请求。

6. 简单请求和非简单请求的区别?预检请求是什么?

要点:简单请求满足:方法为 GET/HEAD/部分 POST,且头与 Content-Type 在安全集合内------直接发出,靠响应头判定是否允许。非简单请求(如 PUT/DELETE、带自定义头、application/json)会先发 OPTIONS 预检,携带将要用的方法与头,服务端以 Access-Control-Allow-* 响应;允许后浏览器才发真实请求。预检可被缓存(Access-Control-Max-Age)减少开销。

7. 跨域时携带 Cookie 要注意什么?

要点:需要服务端 Access-Control-Allow-Credentials: true,且请求带 withCredentials=true;此时 Access-Control-Allow-Origin 不能是通配符 *,必须指定具体域名(浏览器强制)。另外 SameSite Cookie 属性也会影响跨站携带。安全上要谨慎:允许跨源携带凭证等于把接口暴露给该源的前端,确保源可信。

8. 全局异常处理时,未知异常(Exception)该怎么处理?

要点:兜底捕获但严格处理:① 记录完整堆栈到日志并触发告警(这是唯一能发现未预期错误的信号);② 响应返回通用友好消息(如"系统繁忙"+ 错误码/追踪 ID),绝不回传堆栈(安全+体验);③ 可返回追踪 ID 便于用户反馈与日志关联。区分对待:业务异常返回具体码,校验异常返回字段明细,未知异常通用化。

9. 登录校验、权限控制应该放在过滤器还是拦截器?

要点:取决于层次与需求。粗粒度/与容器能力相关的(如 Token 解码、跨域、编码)放过滤器;与业务路由相关的(按路径的登录态校验、细粒度权限、访问日志埋点)放拦截器------它能拿到 Handler 信息(如方法上的权限注解)。若用 Spring Security,则由过滤器链统一承担认证授权。关键:避免同一逻辑两处重复。

10. afterCompletion 和 postHandle 的区别?资源清理放哪个?

要点:postHandle 在 Handler 执行后、视图/响应处理前调用------若 Handler 抛异常则不执行;afterCompletion 在请求完全完成后调用,无论是否异常都会执行。所以资源清理(解绑 ThreadLocal、释放上下文)必须放 afterCompletion------放 postHandle 会在异常路径漏清理,造成内存泄漏或上下文串扰。

相关推荐
泡茶喝茶写代码1 小时前
A股量化数据工程:从 REST 接口到策略信号(第 17 篇):创新高与新低扫描
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
栗子~~1 小时前
java - logstash-logback-encoder 集成 demo
java·开发语言·logback
hmb↑1 小时前
高并发与高可用技术经验
java·spring
楚识科技2 小时前
XML配置OCR接入实战:自定义OCR模板从字段定义到API调用全流程
xml·java·ocr
小蒜学长2 小时前
基于Java的公司采购系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·公司采购系统
lee_tianbai2 小时前
Java SSM 电影票预定系统|完整前后端项目,开箱即用(源码分享)
java·开发语言·数据库
小坏讲微服务2 小时前
Spring Boot 4 新特性全解析:从上手到生产实战
java·spring boot·后端·架构·springboot4
砚底藏山河3 小时前
python量化入门:多周期数据对齐统一时间轴
java·数据库·python·金融·maven
集智飞行3 小时前
解决mavros2 ros2版本cpu占用高的问题
java·服务器·前端