SpringBoot使用@ControllerAdvice進(jìn)行統(tǒng)一處理異常詳解
一、什么是全局異常處理?
在實(shí)際開發(fā)中,程序運(yùn)行時(shí)難免會遇到各種意外。這些意外在 Java 中就表現(xiàn)為異常(Exception)。如果不做統(tǒng)一處理,用戶可能會看到一堆錯誤信息。
常見場景
NullPointerException(空指針異常):前端傳了一個用戶 ID,但后端沒做判空,直接調(diào)用user.getName(),結(jié)果 user 是 null 導(dǎo)致程序崩潰!SQLException(數(shù)據(jù)庫連接失?。簲?shù)據(jù)庫宕機(jī)或網(wǎng)絡(luò)中斷,導(dǎo)致查詢失敗,直接拋出 SQL 異常。NumberFormatException(參數(shù)格式錯誤):前端傳了個字符串"abc",后端試圖用Integer.parseInt()轉(zhuǎn)成數(shù)字失敗!
如果不做處理,會發(fā)生什么
SpringBoot 默認(rèn)會把異常的完整堆棧信息原樣返回給前端,比如:
{
"timestamp": "2025-11-26T12:00:00",
"status": 500,
"error": "Internal Server Error",
"message": "/ by zero",
"path": "/api/calculate"
}
甚至在開發(fā)環(huán)境下,還可能返回幾十行 Java 堆棧,這些會導(dǎo)致以下以下問題:
- 用戶看不懂,
- 前端無法統(tǒng)一處理,
- 更嚴(yán)重的是:可能泄露類名、方法名、服務(wù)器路徑等敏感信息!
有了全局異常處理,就能
- 攔截所有異常,統(tǒng)一返回結(jié)構(gòu)化錯誤信息
- 區(qū)分不同異常類型,返回不同的狀態(tài)碼和提示語
- 自動記錄日志,方便排查問題
- 避免敏感信息外泄,提升系統(tǒng)安全性
最終返回給前端的,可能是這樣一段干凈、友好的 JSON:
{
"code": 400,
"message": "用戶ID不能為空",
"data": null
}
或者:
{
"code": 200,
"message": "訂單創(chuàng)建成功",
"data": true
}
是不是清爽又專業(yè)多了
二、如何做異常處理
傳統(tǒng)做法(不推薦)
每個 Controller 方法里都寫 try-catch:
@GetMapping("/user/{id}")
public ResponseEntity getUser(@PathVariable Long id) {
try {
User user = userService.findById(id);
return ResponseEntity.ok(user);
} catch (UserNotFoundException e) {
return ResponseEntity.status(404).body("用戶不存在");
} catch (Exception e) {
return ResponseEntity.status(500).body("系統(tǒng)錯誤");
}
}
問題很明顯:
- 代碼重復(fù)
- 難維護(hù)
- 容易漏掉
全局異常處理(推薦)
只寫一次,全局生效!
下面主要寫 SpringBoot 的處理。
三、SpringBoot 中如何實(shí)現(xiàn)全局異常處理?
核心就靠一個注解:@ControllerAdvice
第一步:定義統(tǒng)一的錯誤響應(yīng)格式
// 統(tǒng)一返回結(jié)構(gòu),前端好解析
public class ErrorResponse {
private int code;
private String message;
private Object data; // 一般為 null,但保留擴(kuò)展性
public ErrorResponse(int code, String message) {
this.code = code;
this.message = message;
}
// getter / setter 省略(實(shí)際項(xiàng)目中建議用 Lombok)
}
第二步:創(chuàng)建全局異常處理器類
// 1. 創(chuàng)建一個類,加上 @ControllerAdvice 注解
@ControllerAdvice
public class GlobalExceptionHandler {
// 2. 用 @ExceptionHandler 捕獲特定異常
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity handleUserNotFound(UserNotFoundException e) {
ErrorResponse error = new ErrorResponse(404, e.getMessage());
return ResponseEntity.status(404).body(error);
}
// 3. 捕獲所有其他異常(兜底)
@ExceptionHandler(Exception.class)
public ResponseEntity handleGenericException(Exception e) {
ErrorResponse error = new ErrorResponse(500, "服務(wù)器內(nèi)部錯誤,請聯(lián)系管理員");
return ResponseEntity.status(500).body(error);
}
}
第三步:自定義業(yè)務(wù)異常(可選但推薦)
// 自定義業(yè)務(wù)異常
public class BusinessException extends RuntimeException {
private Integer code;
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
// getter省略
}
然后在 Service 層這樣用:
public User findById(Long id) {
if (id == null || id <= 0) {
throw new BusinessException("用戶ID無效");
}
// 查詢數(shù)據(jù)庫...
}
可以說配置全局異常是比較簡單的,可能因?yàn)楹唵?,或者是?xiàng)目里本來就配置的有,所以很多朋友直接忽略掉了這些問題。
四、更智能的異常處理
1. 區(qū)分 Controller 和 REST 接口
如果你既有頁面(返回 HTML),又有 API(返回 JSON),可以用:
@ControllerAdvice(annotations = RestController.class)
public class RestGlobalExceptionHandler {
// 只處理 @RestController 的異常
}
或者指定包路徑:
@ControllerAdvice(basePackages = "com.example.api")
public class ApiExceptionHandler { ... }
2. 處理參數(shù)校驗(yàn)異常(如 @Valid)
SpringBoot 自帶校驗(yàn)框架,校驗(yàn)失敗會拋 MethodArgumentNotValidException:
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldError().getDefaultMessage();
return ResponseEntity.badRequest().body(new ErrorResponse(400, msg));
}
3. 記錄日志(非常重要!)
在 handleGenericException 中加入日志:
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public ResponseEntity handleGenericException(Exception e) {
log.error("系統(tǒng)發(fā)生未預(yù)期異常", e); // 記錄完整堆棧
return ResponseEntity.status(500).body(new ErrorResponse(500, "服務(wù)器開小差啦~"));
}
五、深入理解@ControllerAdvice
很多同學(xué)可能會好奇:為什么加了@ControllerAdvice后,異常就能被自動捕獲呢?
其實(shí)原理很簡單:
Spring MVC在處理請求時(shí),如果 Controller 方法拋出異常Spring會查找有沒有對應(yīng)的異常處理器(@ExceptionHandler)- 查找順序是:先找當(dāng)前 Controller 中的
@ExceptionHandler方法 - 如果當(dāng)前 Controller 沒有,就找
@ControllerAdvice標(biāo)注的類中的@ExceptionHandler方法 - 找到匹配的異常處理器后,調(diào)用對應(yīng)的方法處理異常
最佳實(shí)踐建議
1. 定義清晰的異常體系
// 基礎(chǔ)異常類
public class BaseException extends RuntimeException {
private Integer code;
public BaseException(Integer code, String message) {
super(message);
this.code = code;
}
}
// 各種具體異常
public class BusinessException extends BaseException {
public BusinessException(Integer code, String message) {
super(code, message);
}
}
public class SystemException extends BaseException {
public SystemException(Integer code, String message) {
super(code, message);
}
}
2. 使用枚舉管理錯誤碼
public enum ErrorCode {
SUCCESS(200, "成功"),
PARAM_ERROR(400, "參數(shù)錯誤"),
UNAUTHORIZED(401, "未授權(quán)"),
FORBIDDEN(403, "禁止訪問"),
NOT_FOUND(404, "資源不存在"),
SYSTEM_ERROR(500, "系統(tǒng)錯誤");
private final Integer code;
private final String message;
ErrorCode(Integer code, String message) {
this.code = code;
this.message = message;
}
// getter方法
}
3. 日志記錄要恰當(dāng)
- 業(yè)務(wù)異常:一般用warn級別,不需要記錄堆棧
- 系統(tǒng)異常:用error級別,需要記錄堆棧信息
完整示例代碼
// 統(tǒng)一返回結(jié)果
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private Integer code;
private String message;
private T data;
public static Result success(T data) {
return new Result<>(200, "成功", data);
}
public static Result error(Integer code, String message) {
return new Result<>(code, message, null);
}
}
// 全局異常處理器
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 處理業(yè)務(wù)異常
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e) {
logger.warn("業(yè)務(wù)異常:code={}, message={}", e.getCode(), e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
// 處理參數(shù)校驗(yàn)異常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error(400, message);
}
// 處理所有其他異常
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
logger.error("系統(tǒng)異常:", e);
return Result.error(500, "系統(tǒng)繁忙,請稍后重試");
}
}
總結(jié)
統(tǒng)一格式:前后端約定好錯誤結(jié)構(gòu),溝通更順暢
減少重復(fù)代碼:不用每個方法寫 try-catch
提升健壯性:兜底處理防止系統(tǒng)崩潰,還能記錄日志
到此這篇關(guān)于SpringBoot使用@ControllerAdvice進(jìn)行統(tǒng)一處理異常詳解的文章就介紹到這了,更多相關(guān)SpringBoot異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Spring循環(huán)依賴原理與bean的生命周期圖文案例詳解
這篇文章主要介紹了Spring循環(huán)依賴原理與bean的生命周期圖文案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
記一次用IDEA打開java項(xiàng)目后不能運(yùn)行的解決方法
這篇文章主要介紹了記一次用IDEA打開java項(xiàng)目后不能運(yùn)行的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
JAVA實(shí)現(xiàn)數(shù)字大寫金額轉(zhuǎn)換的方法
這篇文章主要介紹了JAVA實(shí)現(xiàn)數(shù)字大寫金額轉(zhuǎn)換的方法,涉及java針對字符串與數(shù)組的遍歷與轉(zhuǎn)換相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07

