SpringBoot 统一异常处理
统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的。@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
代码示例
ExceptionAdvice
接口返回为数据时,需要加 @ResponseBody 注解!!!
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import springbook.model.Result;
@Slf4j
@ResponseBody
@ControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler
public Result handlerException(Exception e) {
log.error("发生异常,e: {}", e);
return Result.fail("内部错误");
}
@ExceptionHandler
public Result handlerException(NullPointerException e) {
log.error("发生异常,e:", e);
return Result.fail("发生空指针异常");
}
@ExceptionHandler
public Result handlerException(ArithmeticException e) {
log.error("发生异常,e:", e);
return Result.fail("发生算术异常");
}
}
Result
import lombok.Data;
import springbook.enums.ResultStatus;
@Data
public class Result<T> {
private ResultStatus code;
private String errMsg; // 错误信息,如果业务成功,errMsg 为空
private T data;
public static <T> Result success(T data) {
Result result = new Result<>();
result.setCode(ResultStatus.SUCCESS);
result.setData(data);
return result;
}
public static Result fail(String msg) {
Result result = new Result<>();
result.setCode(ResultStatus.FAIL);
result.setErrMsg(msg);
return result;
}
public static Result fail(ResultStatus resultStatus, String msg) {
Result result = new Result<>();
result.setCode(resultStatus);
result.setErrMsg(msg);
return result;
}
}
TestController
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/t1")
public String t1() {
int result = 10 / 0;
return "string";
}
@RequestMapping("/t2")
public Integer t2() {
String str = null;
System.out.println(str.length());
return 1;
}
@RequestMapping("/t3")
public Boolean t3() {
Integer[] integers = new Integer[]{1, 2, 3, 4};
System.out.println(integers[5]);
return true;
}
}
问题
我们知道,ArithmeticException 和 NullPointerException 都属于 RuntimeException,同时 RuntimeException 又属于 Exception。
那么为什么同时有处理 ArithmeticException 和 Exception 的方法时,针对 ArithmeticException 异常,会调用处理 ArithmeticException 异常的方法而不是调用处理 Exception 异常的方法?
那么为什么同时有处理 NullPointerException 和 Exception 的方法时,针对 NullPointerException 异常,会调用处理 NullPointerException 异常的方法而不是调用处理 Exception 异常的方法?
结合源码了解问题源头
下面通过 Debug 的方式,结合源码来剖析为什么会出现上面的问题。
以上面代码的访问 test/t1 路径下方法导致 ArithmeticException 异常为例:
经过 Debug,我了解到,处理异常会走到下图所示的 ExceptionHandlerMethodResolver 类里面的 getMapperMethod()方法:
在刚进入该方法的时候,经过第一个黄色框内代码的处理后,会把当前具有处理异常的方法结合当前发生的异常进行匹配,最终匹配到两个合适类型的异常,分别是 Exception 异常和 ArithmeticException 异常:
但是在经过第二个黄色框内的代码处理后,会对刚刚匹配到的异常进行排序,根据异常的深度进行排序,并返回深度最浅的异常(即返回最合适的异常)。
经过第二个黄色框内的代码处理后,matches 里面的内容顺序发生变动:
此时,我们就知道了为什么同时有处理 ArithmeticException 和 Exception 的方法时,针对 ArithmeticException 异常,会调用处理 ArithmeticException 异常的方法而不是调用处理 Exception 异常的方法;以及为什么同时有处理 NullPointerException 和 Exception 的方法时,针对 NullPointerException 异常,会调用处理 NullPointerException 异常的方法而不是调用处理 Exception 异常的方法。
优点
- 提高用户体验 友好的错误信息:通过统一异常处理,可以为前端或其他服务提供一致的、易于理解的错误信息,而不是直接暴露后端的堆栈跟踪或技术细节,从而提升用户体验。 避免敏感信息泄露:统一处理可以避免在异常响应中不小心泄露敏感信息,如数据库表结构、内部实现细节等。
- 业务逻辑和异常处理逻辑解耦 代码结构清晰:将异常处理逻辑从业务逻辑中分离出来,使得业务代码更加专注于业务逻辑的实现,而异常处理代码则专注于异常的捕获、处理和响应。 易于维护:当需要修改异常处理逻辑时,只需在统一异常处理类中修改,而无需在多个业务代码中逐一修改,降低了维护成本。
- 减少冗余代码 避免重复编写:在传统的 Java 开发中,异常处理通常是分散在代码的各个部分中的。而使用统一异常处理可以避免在每个可能抛出异常的地方编写重复的异常处理代码。 提高代码复用性:通过定义全局的异常处理类,可以实现对多种异常的统一处理,提高代码的复用性。
- 便于代码风格统一 风格一致:统一异常处理有助于保持代码风格的一致性,使得整个项目的代码更加规范、整洁。 提升可读性:对于其他开发者来说,阅读和理解统一异常处理的代码也更为容易。
- 便于异常监控和日志记录 集中监控:通过统一异常处理,可以更容易地实现对异常情况的集中监控和统计,为后续的故障排查和性能优化提供依据。 详细日志:在异常处理过程中,可以记录详细的异常信息和上下文信息,便于后续的日志分析和问题定位。


