Spring Boot 全局異常處理最佳實踐
在日常開發中,異常處理幾乎是繞不過去的一個話題。尤其在 后端 API 項目 中,如果沒有統一的異常處理機制,很容易出現以下問題:
- Controller 層代碼里充斥著
try-catch
,顯得冗余。 - 前端拿到的錯誤響應格式不一致,增加解析成本。
- 系統異常與業務異常混雜,難以追蹤和排查。
因此,在 Spring Boot 項目中,我們通常會通過 全局異常處理 來收斂所有錯誤,保證接口返回結構的統一性,并簡化開發。
一、為什么需要全局異常處理?
在沒有全局異常處理之前,開發者常常這樣寫代碼:
@GetMapping("/{id}")
public User getUser(@PathVariable int id) {try {return userService.findById(id);} catch (Exception e) {e.printStackTrace();return null;}
}
問題在于:
try-catch
邏輯冗余,重復代碼太多。- 一旦拋出異常,返回值不明確,前端無法準確感知錯誤信息。
👉 解決方案就是使用 Spring Boot 提供的全局異常處理機制:@ControllerAdvice + @ExceptionHandler
。
二、定義統一返回結果
首先定義一個通用的響應對象 ApiResponse
,用于統一接口返回格式:
public class ApiResponse<T> {private int code;private String message;private T data;public ApiResponse(int code, String message, T data) {this.code = code;this.message = message;this.data = data;}public static <T> ApiResponse<T> success(T data) {return new ApiResponse<>(200, "success", data);}public static <T> ApiResponse<T> error(int code, String message) {return new ApiResponse<>(code, message, null);}// getter & setter
}
這樣一來,無論成功還是失敗,都能保證返回結果的結構一致。
三、自定義業務異常
除了系統異常(NullPointerException
、SQLException
等),我們還需要定義 業務異常,用于明確業務邏輯錯誤:
public class BusinessException extends RuntimeException {private int code;public BusinessException(int code, String message) {super(message);this.code = code;}public int getCode() {return code;}
}
例如:用戶不存在、余額不足、參數非法等,都可以通過 BusinessException
來拋出。
四、編寫全局異常處理器
接下來,通過 @RestControllerAdvice
來統一捕獲異常:
@RestControllerAdvice
public class GlobalExceptionHandler {// 處理業務異常@ExceptionHandler(BusinessException.class)public ApiResponse<?> handleBusinessException(BusinessException ex) {return ApiResponse.error(ex.getCode(), ex.getMessage());}// 處理參數校驗異常@ExceptionHandler(MethodArgumentNotValidException.class)public ApiResponse<?> handleValidException(MethodArgumentNotValidException ex) {String msg = ex.getBindingResult().getFieldError().getDefaultMessage();return ApiResponse.error(400, msg);}// 兜底異常處理@ExceptionHandler(Exception.class)public ApiResponse<?> handleException(Exception ex) {ex.printStackTrace(); // 可接入日志系統return ApiResponse.error(500, "服務器內部錯誤");}
}
這樣,不管是業務異常還是系統異常,都會走到全局處理器,保證返回結果的統一性。
五、使用示例
Controller
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("/{id}")public ApiResponse<String> getUser(@PathVariable int id) {if (id == 0) {throw new BusinessException(404, "用戶不存在");}return ApiResponse.success("用戶ID: " + id);}
}
請求與響應
- 正常請求:
{"code": 200,"message": "success","data": "用戶ID: 1"
}
- 業務異常:
{"code": 404,"message": "用戶不存在","data": null
}
- 系統異常:
{"code": 500,"message": "服務器內部錯誤","data": null
}
六、總結
通過 全局異常處理,我們實現了以下目標:
- 統一返回結構,方便前端解析。
- 集中管理異常,減少冗余
try-catch
。 - 區分業務異常與系統異常,提升代碼可維護性。
- 可擴展性強,后續可以接入日志系統(如 Logback、ELK)或異常監控平臺(如 Sentry)。
建議在實際項目中,將 全局異常處理 作為基礎框架的一部分,避免每個 Controller 重復造輪子。
七、日志落庫 / ELK 接入最佳實踐
在真實的生產環境中,僅僅返回統一的錯誤信息還不夠。我們還需要對異常進行持久化存儲與分析,以便后續排查問題和改進系統。
常見的做法有兩種:
1. 日志落庫(數據庫存儲)
在全局異常處理器中,可以將異常信息寫入數據庫(例如 MySQL、PostgreSQL):
@ExceptionHandler(Exception.class)
public ApiResponse<?> handleException(Exception ex, HttpServletRequest request) {// 記錄日志信息ErrorLog log = new ErrorLog();log.setUri(request.getRequestURI());log.setMethod(request.getMethod());log.setMessage(ex.getMessage());log.setStackTrace(Arrays.toString(ex.getStackTrace()));log.setCreateTime(LocalDateTime.now());errorLogRepository.save(log); // JPA 或 MyBatis 保存return ApiResponse.error(500, "服務器內部錯誤");
}
其中 ErrorLog
可以定義為:
@Entity
public class ErrorLog {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String uri;private String method;private String message;private String stackTrace;private LocalDateTime createTime;// getter & setter
}
這樣就能將異常詳細信息落到數據庫,方便后續查詢與統計。
2. 接入 ELK(Elasticsearch + Logstash + Kibana)
如果系統日志量較大,推薦接入 ELK,實現實時日志收集與可視化。
(1)配置 Logback 輸出 JSON 日志
在 logback-spring.xml
中使用 logstash-logback-encoder
輸出 JSON:
<configuration><appender name="LOGSTASH" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/app-log.json</file><encoder class="net.logstash.logback.encoder.LoggingEventEncoder"/></appender><root level="INFO"><appender-ref ref="LOGSTASH"/></root>
</configuration>
(2)通過 Logstash 收集日志
配置 Logstash(logstash.conf):
input {file {path => "/usr/local/app/logs/app-log.json"codec => json}
}output {elasticsearch {hosts => ["http://localhost:9200"]index => "springboot-error-%{+YYYY.MM.dd}"}
}
(3)在 Kibana 可視化
通過 Kibana Dashboard,可以實現:
- 錯誤趨勢圖
- 按 API 維度統計異常數量
- 按時間維度分析錯誤高峰
- 搜索具體異常堆棧
3. 最佳實踐建議
- 開發環境:控制臺打印異常即可,方便調試。
- 測試環境:日志落庫,方便 QA 排查問題。
- 生產環境:接入 ELK,實時收集和可視化,必要時配合 告警系統(如飛書/釘釘機器人、Prometheus Alertmanager)。
八、飛書機器人告警接入
在生產環境中,僅僅記錄日志還不夠。當發生嚴重異常時,我們希望能 實時收到告警通知,避免問題被埋沒。常見的方式就是接入 企業 IM 工具(如飛書、釘釘、企業微信)。
下面以 飛書自定義機器人 為例,展示如何在全局異常處理器中推送告警。
1. 創建飛書自定義機器人
- 打開飛書群聊 → 設置 → 群機器人 → 添加機器人 → 選擇 自定義機器人。
- 復制生成的 Webhook 地址,類似:
https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
2. 編寫工具類(推送異常消息)
使用 RestTemplate
發送 POST 請求:
@Component
public class FeishuBotNotifier {private static final String WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx";private final RestTemplate restTemplate = new RestTemplate();public void sendAlert(String title, String content) {Map<String, Object> body = new HashMap<>();body.put("msg_type", "text");Map<String, String> text = new HashMap<>();text.put("text", String.format("【異常告警】\n標題: %s\n詳情: %s", title, content));body.put("content", text);restTemplate.postForEntity(WEBHOOK_URL, body, String.class);}
}
3. 在全局異常處理器中調用
當捕獲到異常時,推送到飛書:
@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {private final FeishuBotNotifier feishuBotNotifier;@ExceptionHandler(Exception.class)public ApiResponse<?> handleException(Exception ex, HttpServletRequest request) {// 構造告警信息String title = "SpringBoot 服務異常";String content = String.format("URI: %s\nMethod: %s\n錯誤: %s",request.getRequestURI(), request.getMethod(), ex.getMessage());// 發送飛書告警feishuBotNotifier.sendAlert(title, content);ex.printStackTrace();return ApiResponse.error(500, "服務器內部錯誤");}
}
4. 飛書群內效果
觸發異常后,群內會收到類似消息:
【異常告警】
標題: SpringBoot 服務異常
詳情:
URI: /user/0
Method: GET
錯誤: 用戶不存在
九、總結(終極版 🚀)
到這里,我們的 全局異常管理方案 已經形成了一整套閉環:
- 全局異常處理:統一返回格式,簡化開發。
- 業務異常區分:明確業務錯誤與系統錯誤。
- 日志落庫:異常可持久化追溯。
- ELK 接入:日志可視化分析,支持查詢與報表。
- 飛書機器人告警:重大異常實時通知,提高運維響應速度。
👉 這樣一套機制,基本涵蓋了 從接口開發 → 異常收斂 → 日志分析 → 實時告警 的完整鏈路,既保證了系統的可維護性,也提升了線上運維的響應效率。