springmvc 全局异常处理【补充】

核心结论

@RestControllerAdvice + @ExceptionHandler 属于 SpringMVC(DispatcherServlet)内部的异常处理器。
只要异常跑出了 DispatcherServlet 的管辖范围,全局异常处理器就抓不到。

先看完整请求时序 + 管辖边界:

复制代码
Tomcat容器
    ↓
Filter 1 → Filter 2         ←【DispatcherServlet外面!Spring全局异常管不到Filter】
    ↓
DispatcherServlet.doDispatch()  // SpringMVC 管辖范围开始
    ├─Interceptor.preHandle()
    ├─Controller方法
    ├─Interceptor.postHandle()
    ├─渲染视图
    └─finally { Interceptor.afterCompletion() }
DispatcherServlet管辖范围结束
    ↓
Filter 链反向退出
    ↓
Tomcat容器



1、Filter过滤器抛出异常 → 抓不到

原因

Filter 是 Servlet 规范组件 ,执行在 DispatcherServlet 执行之前

此时 SpringMVC 还没接手请求。异常直接往上抛给 Tomcat / Web容器,根本不会走到 Spring 的全局异常处理链路。

@RestControllerAdvice 完全收不到。

现象:浏览器直接返回 Tomcat 默认500白页,不会返回你定义的JSON错误体。

解决办法:Filter 内部自己 try‑catch,手动写出错误响应

java 复制代码
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    try {
        //你的校验逻辑,可能抛异常
        chain.doFilter(request,response);
    }catch (Exception e){
        HttpServletResponse resp = (HttpServletResponse)response;
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().write("{\"code\":500,\"msg\":\"过滤器异常:"+e.getMessage()+"\"}");
    }
}



先分清两个地方能干啥

  • Filter(过滤器) :在 SpringMVC、拦截器之前执行。属于 Servlet 最外层。
  • Interceptor.preHandle:已经进到 SpringMVC(DispatcherServlet) 里面了。

什么样的校验适合放 Filter?

适合越早拦截越好的校验:

  • token / 签名校验
  • 请求头校验
  • 跨域
  • 黑名单 IP
  • 非法参数快速拒绝
为什么优先放 Filter?
  1. 校验失败直接拦住请求,连 Controller、Spring 都不会进,省资源;
  2. 但是!Filter抛出异常进不去 @RestControllerAdvice全局异常,所以 Filter 里面必须自己 try‑catch,手动返回 JSON;

preHandle 适合放什么校验?

适合需要 Spring 上下文、拿到控制器 HandlerMethod 的校验:

  • 判断当前接口是不是加了某个注解(比如 @RequiresLogin
  • 获取 Controller方法信息,做权限控制

这种校验你放 Filter 拿不到 Controller 方法信息,只能放拦截器 preHandle



preHandle 抛业务异常,不要手动写 response

❌错误写法(千万不要这么干)

java 复制代码
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    // 校验token失败,这个 if(true) 是我写着玩的,实际开发中,按你的条件判断即可
    if(true){ 
        // 自己手动输出响应
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}");
        // 一旦往 response 写了内容,`response.isCommitted()=true`,全局异常处理器就失效了。
        
        // 手写响应后,一定要写一个return false
        return false;
    }
    return true;
}


1. if 里面的`return false` 明确告诉 Spring:停止后续所有流程,不去执行Controller
2. postHandle 不会执行;
3. afterCompletion 会执行
4. 如果 if 里面不写 return false,就会执行外面的 return true,
   代码还会跑进 Controller 执行业务!白白浪费性能,甚至产生脏数据
   Controller 里面又往 response 写数据  导致→  报错:response 已经提交,不能重复写响应

问题在哪?

当你调用 response.getWriter().write(),Tomcat 就会把响应刷出去

response.isCommitted() = true响应已经提交

后面就算你再抛出异常:

java 复制代码
throw new BusinessException(401,"未登录");

Spring 的全局异常处理器 @RestControllerAdvice 已经不能再修改返回结果了

响应流已经关闭,全局异常处理失效。

现象:有时候前端收到一半报文、或者乱码,或者异常处理器根本不走。

✅正确写法:抛出异常,交给全局异常处理

java 复制代码
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    if(true){
        // 只抛异常,不碰response!!
        throw new BusinessException(401,"未登录");
        // return false; //永远执行不到,多余,可以删掉

		"补充"
			抛出异常的分支,不需要 return false,
			`throw` 直接终止当前方法,不会继续往下,Spring 自动中断流程,不走 Controller。
    }
    return true;
}

特点:

  1. 不要手动往response写任何内容
  2. DispatcherServlet 捕获这个异常;
  3. 异常进到你的 @RestControllerAdvice,统一格式化返回 JSON;
  4. 返回 false 就不用写了,抛异常本身就中断流程。

⚠️注意:preHandle抛出异常之后,return 语句永远不会执行。



一张决策图告诉你校验放哪里

复制代码
校验是否需要拿到Controller方法/注解?
    ├─是 → 放 Interceptor preHandle,抛异常,不要手动写response
    └─否 → 放 Filter,校验失败try‑catch手动输出JSON

总结

  1. Filter :校验失败 → 自己写response返回JSON,不要抛异常
  2. Interceptor‑preHandle :校验失败 → 抛自定义异常,不要手动写response,交给@RestControllerAdvice处理
  3. afterCompletion:绝对不能向外抛异常,内部必须try‑catch
相关推荐
Wang's Blog25 分钟前
Vibe Coding一人即团队系列35: 基于Claude Code的Spring Boot项目初始化实践
java·spring boot·后端
Dovis(誓平步青云)43 分钟前
拍视频前先把镜头想清楚:做一个分镜取景辅助器
android·java·服务器·javascript·人工智能
AI人工智能+电脑小能手1 小时前
大白话说Java设计模式-45-解释器模式(源码剖析篇)
java·设计模式·解释器模式·源码分析·pattern·spel·javacc
吴声子夜歌1 小时前
Java——开发中通用的方法和准则(二)
java·开发语言·php
袁震1 小时前
HarmonyOS 应用包体积优化与上架自检实战:从 76.2MB 到 3.6MB
java·华为·性能优化·harmonyos
zcmodeltech1 小时前
反应装置模型控制系统设计与实现:多设备协同联动方案
java·网络·数据库·stm32·嵌入式硬件·能源·制造
码视野2 小时前
基于 Spring Boot + Vue3 的【城市地下燃气管网微泄漏感知与相邻地下空间燃爆预警中台】设计与实现(含PRD/三端高保真源码/大屏)
java·前端·人工智能·spring boot·后端
2601_962071572 小时前
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
java·开发语言·数据库
梦想的旅途22 小时前
如何高效调用企业微信通讯录API管理组织架构
java·开发语言·企业微信