c -> true 导致异常返回 404 问题排查
最近在新开一个项目,在新建基础架构的时候遇到了一个很有意思的问题:
- 接口正常访问时返回
200 - Controller 中人为制造异常后
- 后端日志明确打印了异常
- 但前端(Apifox)收到的却是
404 - 理论上应该返回
500
最终排查发现:
根因是 Spring Boot 全局路径前缀配置污染了系统内置的
/error路由。
这个问题非常具有迷惑性,记录一下完整排查过程。
一、问题现象
Controller层制造异常,用于测试异常处理 :
int a = 1 / 0;二、后端日志
java.lang.ArithmeticException: / by zero完整调用链中可以看到:
Servlet.service() for servlet [dispatcherServlet] threw exception说明:
- Controller 已经执行
- 异常已经抛出
- Spring MVC 已经捕获异常
按正常逻辑:
应该返回:
500 Internal Server Error但实际上:
Apifox 返回:
404 Not Found三、第一次怀疑方向
一开始怀疑:
1. AOP 吞异常
因为项目里有:
@Around public Object around(ProceedingJoinPoint point)很多项目会写成:
try { return point.proceed(); } catch (Exception e) { } return null;这种会导致:
- Spring 找不到返回视图
- 最终变成 404
但检查后发现:AOP 没问题
2. 全局异常处理器
怀疑:
@ControllerAdvice或者:
@ExceptionHandler吞掉异常
检查后排除
四、真正的问题
项目中有如下配置:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { // 为所有 Controller 添加 /api 前缀 configurer.addPathPrefix("/api", c -> true); } }问题就出在:c -> true
五、问题原理分析
这段代码的意思是:
给所有 Controller 都加
/api前缀。
注意:
这里的“所有 Controller”不仅仅包括:
- 自己写的 Controller
还包括:
- Spring Boot 内置 Controller
- BasicErrorController
/error- actuator
- swagger 等
六、为什么正常接口还能返回 200?
正常情况下请求流程:
DispatcherServlet
-> Controller
-> return AjaxResult.success()
-> 200
因为没有异常:
- 不会进入
/error - 所以一切正常
因此返回:200 OK
七、为什么异常时变成 404?
出现异常后,Spring MVC 流程:
DispatcherServlet
-> Controller 抛异常
-> 异常解析器
-> 转发到 /error
正常应该:
/error
-> BasicErrorController
-> 返回 500
但由于:
addPathPrefix("/api", c -> true)Spring 内置:
/error
也被加了前缀:
/api/error
现在问题就来了:
Spring Boot 默认真正注册的是:/error;不是/api/error
于是异常发生后:
/api/error
找不到。
最终:
404 Not Found
八、问题本质
这里的 404:并不是业务接口不存在;而是异常处理路由不存在
九、最终解决方案
不要给所有 Controller 都加前缀。
应该只给自己的业务 Controller 加。
修改为:
@Configuration
public class WebConfig implements WebMvcConfigurer {@Override
public void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix(
"/api",
c -> c.getPackageName().startsWith("com.bs.web.controller")
);
}
}
这样:
- 自己业务 Controller:
/api/xxx- Spring 系统 Controller:
/error不会受影响
总结
c -> true 这种全匹配不要随便使用,Spring Boot 的/error一旦被污染各种诡异问题都会出现
