@controlleradvice是spring boot中统一捕获controller层异常的最有效方式,需配合@exceptionhandler使用,按业务分组定义、限定basepackages范围,返回responseentity,且不处理filter/interceptor等mvc外异常。

Spring Boot中用@ControllerAdvice捕获所有Controller异常
绝大多数Web应用的业务异常都发生在Controller层或其调用链中,@ControllerAdvice是最直接有效的统一拦截点。它能覆盖所有@RestController和@Controller类抛出的未被捕获异常,包括RuntimeException及其子类、受检异常(如果没在方法签名中声明)。
- 必须配合
@ExceptionHandler使用,每个方法指定处理一类异常,例如@ExceptionHandler(NullPointerException.class) - 推荐按业务分组定义多个
@ControllerAdvice类(如ValidationAdvice、BusinessExceptionAdvice),避免单个类膨胀 - 注意
@ControllerAdvice默认作用于所有包,可通过basePackages限定扫描范围,防止误拦截第三方库异常 - 返回值建议用
ResponseEntity<result></result>而非String或ModelAndView,便于统一状态码和JSON结构
区分@ResponseStatus和@ExceptionHandler的适用场景
@ResponseStatus只适合简单、无逻辑分支的异常映射,比如404、401这类标准HTTP状态码;而@ExceptionHandler支持完整Java方法体,可记录日志、调用服务、动态构造响应体。
-
@ResponseStatus(code = HttpStatus.NOT_FOUND)只能加在异常类上(如自定义ResourceNotFoundException),无法做条件判断或补充字段 -
@ExceptionHandler方法内可调用log.error("ID={} not found", id, e),也可根据环境决定是否返回堆栈信息 - 两者不互斥:可为异常类标注
@ResponseStatus作兜底,再用@ExceptionHandler覆盖特定逻辑 - 若同时存在,
@ExceptionHandler优先级更高,@ResponseStatus会被忽略
全局异常处理器必须避开Filter和Interceptor中的异常
@ControllerAdvice只拦截进入DispatcherServlet之后的异常,Filter、Interceptor、Servlet容器层(如400 Bad Request)、跨域预检失败等都不在其范围内。
- Filter中抛出的异常会直接触发容器默认错误页(如Tomcat的Whitelabel Error Page),需在
web.xml或ErrorPageRegistrar中注册/error路径接管 - Interceptor的
preHandle异常不会被@ControllerAdvice捕获,应在内部try-catch并转为ResponseEntity返回 - JSON解析失败(如字段类型不匹配)触发的是
HttpMessageNotReadableException,属于Spring MVC异常体系,可被@ExceptionHandler捕获,但需确保该异常未被更早的HandlerExceptionResolver吞掉
自定义异常基类要重写fillInStackTrace()提升性能
频繁抛出异常时,Throwable.fillInStackTrace()是主要开销来源,尤其在日志记录或监控埋点密集的场景下。业务异常通常不需要完整堆栈,跳过它能降低30%+异常构造耗时。
立即学习“Java免费学习笔记(深入)”;
- 继承
RuntimeException后,在构造函数中显式调用super(null),再重写fillInStackTrace()返回this - 仅对明确不需排查调用链的异常(如
ParamInvalidException、AuthFailedException)做此优化 - 保留堆栈的异常(如
ServiceUnavailableException)仍应调用父类fillInStackTrace() - 注意:IDE调试时可能看不到堆栈,需依赖日志中的
cause或主动打印e.getStackTrace()
@ControllerAdvice,得先确认它真正在MVC流程里。比如CompletableFuture异步回调里的异常、ScheduledTask中的异常、甚至MyBatis的SQLException包装链,都可能绕过常规拦截路径。










