Spring?Boot全局異常處理實(shí)戰(zhàn)指南
引言
在開(kāi)發(fā) Spring Boot 項(xiàng)目時(shí),你是否遇到過(guò)這些問(wèn)題?
- ? 不同 Controller 重復(fù)寫(xiě)
try-catch
- ? 異常信息格式不統(tǒng)一,前端難以解析
- ? 系統(tǒng)內(nèi)部錯(cuò)誤直接暴露給用戶(hù)(如堆棧信息)
- ? 404、500 等狀態(tài)碼處理混亂
這些問(wèn)題不僅影響用戶(hù)體驗(yàn),還可能帶來(lái)安全風(fēng)險(xiǎn)。
全局異常處理就是 Spring Boot 為我們提供的“統(tǒng)一調(diào)度中心”,它能在異常發(fā)生時(shí)自動(dòng)攔截、處理,并返回友好的響應(yīng)。
本文將帶你從零開(kāi)始,構(gòu)建一套生產(chǎn)級(jí)的全局異常處理機(jī)制,并結(jié)合實(shí)際代碼,讓你徹底掌握這一核心技能。
一、為什么需要全局異常處理?
想象一個(gè)電商系統(tǒng):
- 用戶(hù)下單時(shí)庫(kù)存不足 → 返回“庫(kù)存不足,請(qǐng)稍后再試”
- 訂單ID格式錯(cuò)誤 → 返回“訂單不存在”
- 數(shù)據(jù)庫(kù)連接失敗 → 記錄日志,返回“系統(tǒng)繁忙,請(qǐng)稍后重試”
如果沒(méi)有統(tǒng)一處理,每個(gè)接口都要寫(xiě)類(lèi)似的 try-catch
,代碼重復(fù)且難以維護(hù)。
而有了全局異常處理,就像設(shè)立了一個(gè)“客戶(hù)服務(wù)中心”,所有異常都由它統(tǒng)一接待、分類(lèi)處理、禮貌回應(yīng)。
二、核心技術(shù):@ControllerAdvice和@ExceptionHandler
Spring Boot 提供了兩個(gè)核心注解來(lái)實(shí)現(xiàn)全局異常處理:
注解 | 作用 |
---|---|
@ControllerAdvice | 定義全局異常處理器(可作用于所有 Controller) |
@ExceptionHandler | 指定處理某類(lèi)異常的方法 |
三、實(shí)戰(zhàn):構(gòu)建統(tǒng)一異常處理機(jī)制
3.1 定義統(tǒng)一返回格式
@Data @AllArgsConstructor @NoArgsConstructor public class ApiResponse<T> { private int code; private String message; private T data; // 成功響應(yīng) public static <T> ApiResponse<T> success(T data) { return new ApiResponse<>(200, "success", data); } // 失敗響應(yīng) public static <T> ApiResponse<T> error(int code, String message) { return new ApiResponse<>(code, message, null); } }
3.2 自定義業(yè)務(wù)異常
// 業(yè)務(wù)異?;?lèi) public class BusinessException extends RuntimeException { private int code; public BusinessException(int code, String message) { super(message); this.code = code; } public BusinessException(String message) { super(message); this.code = 500; } // getter public int getCode() { return code; } }
使用示例:
@Service public class OrderService { public void createOrder(Long productId, Integer quantity) { if (quantity <= 0) { throw new BusinessException(400, "購(gòu)買(mǎi)數(shù)量必須大于0"); } // ... 其他邏輯 } }
3.3 全局異常處理器(核心)
@RestControllerAdvice // 等價(jià)于 @ControllerAdvice + @ResponseBody @Slf4j public class GlobalExceptionHandler { /** * 處理自定義業(yè)務(wù)異常 */ @ExceptionHandler(BusinessException.class) public ApiResponse<String> handleBusinessException(BusinessException e) { log.warn("業(yè)務(wù)異常: {}", e.getMessage()); return ApiResponse.error(e.getCode(), e.getMessage()); } /** * 處理參數(shù)校驗(yàn)異常(@Valid) */ @ExceptionHandler(MethodArgumentNotValidException.class) public ApiResponse<String> handleValidationException(MethodArgumentNotValidException e) { // 獲取第一個(gè)錯(cuò)誤信息 String errorMessage = e.getBindingResult() .getFieldErrors() .stream() .map(FieldError::getDefaultMessage) .findFirst() .orElse("參數(shù)校驗(yàn)失敗"); log.warn("參數(shù)校驗(yàn)異常: {}", errorMessage); return ApiResponse.error(400, errorMessage); } /** * 處理空指針異常 */ @ExceptionHandler(NullPointerException.class) public ApiResponse<String> handleNullPointerException(NullPointerException e) { log.error("空指針異常", e); return ApiResponse.error(500, "系統(tǒng)內(nèi)部錯(cuò)誤,請(qǐng)聯(lián)系管理員"); } /** * 處理所有未被捕獲的異常(兜底) */ @ExceptionHandler(Exception.class) public ApiResponse<String> handleException(Exception e) { log.error("未處理異常", e); return ApiResponse.error(500, "系統(tǒng)繁忙,請(qǐng)稍后重試"); } /** * 處理 404 Not Found */ @ExceptionHandler(NoHandlerFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ApiResponse<String> handle404(NoHandlerFoundException e) { log.warn("請(qǐng)求路徑不存在: {}", e.getRequestURL()); return ApiResponse.error(404, "請(qǐng)求的資源不存在"); } }
3.4 啟用 404 異常捕獲(重要?。?/h3>
默認(rèn)情況下,404 異常不會(huì)進(jìn)入 @ExceptionHandler
,需在配置文件中開(kāi)啟:
# application.yml spring: mvc: throw-exception-if-no-handler-found: true # 找不到處理器時(shí)拋出異常 web: resources: add-mappings: false # 關(guān)閉默認(rèn)靜態(tài)資源映射(可選,更嚴(yán)格)
四、測(cè)試驗(yàn)證
4.1 創(chuàng)建測(cè)試 Controller
@RestController @RequestMapping("/api") public class TestController { @GetMapping("/business") public ApiResponse<String> businessError() { throw new BusinessException(400, "用戶(hù)名已存在"); } @PostMapping("/validate") public ApiResponse<String> validate(@Valid @RequestBody UserForm form) { return ApiResponse.success("驗(yàn)證通過(guò)"); } @GetMapping("/null") public ApiResponse<String> nullPointer() { String str = null; str.length(); // 觸發(fā) NullPointerException return ApiResponse.success("success"); } @GetMapping("/unknown") public ApiResponse<String> unknown() { throw new RuntimeException("未知錯(cuò)誤"); } }
class UserForm { @NotBlank(message = "用戶(hù)名不能為空") private String username; @Min(value = 18, message = "年齡不能小于18歲") private Integer age; // getter & setter }
4.2 測(cè)試結(jié)果
請(qǐng)求 | 響應(yīng) |
---|---|
GET /api/business | {"code":400,"message":"用戶(hù)名已存在","data":null} |
POST /api/validate (無(wú)參數(shù)) | {"code":400,"message":"用戶(hù)名不能為空","data":null} |
GET /api/null | {"code":500,"message":"系統(tǒng)內(nèi)部錯(cuò)誤,請(qǐng)聯(lián)系管理員","data":null} |
GET /api/unknown | {"code":500,"message":"系統(tǒng)繁忙,請(qǐng)稍后重試","data":null} |
GET /api/not-exist | {"code":404,"message":"請(qǐng)求的資源不存在","data":null} |
五、高級(jí)技巧與生產(chǎn)實(shí)踐
5.1 異常分類(lèi)管理
你可以為不同模塊定義不同的異常處理器:
// 用戶(hù)模塊異常處理器 @ControllerAdvice("com.example.controller.user") public class UserExceptionHandler { ... } // 訂單模塊異常處理器 @ControllerAdvice("com.example.controller.order") public class OrderExceptionHandler { ... }
5.2 結(jié)合 AOP 記錄異常日志
@Aspect @Component public class ExceptionLogAspect { @AfterThrowing(pointcut = "@within(org.springframework.web.bind.annotation.RestController)", throwing = "ex") public void logException(JoinPoint joinPoint, Exception ex) { String methodName = joinPoint.getSignature().getName(); log.error("方法 {} 發(fā)生異常: {}", methodName, ex.getMessage()); } }
5.3 返回錯(cuò)誤碼枚舉(推薦)
public enum ErrorCode { SUCCESS(200, "成功"), BAD_REQUEST(400, "請(qǐng)求參數(shù)錯(cuò)誤"), UNAUTHORIZED(401, "未授權(quán)"), FORBIDDEN(403, "禁止訪(fǎng)問(wèn)"), NOT_FOUND(404, "資源不存在"), SERVER_ERROR(500, "系統(tǒng)內(nèi)部錯(cuò)誤"); private final int code; private final String message; ErrorCode(int code, String message) { this.code = code; this.message = message; } // getter }
使用:
throw new BusinessException(ErrorCode.BAD_REQUEST);
總結(jié):全局異常處理的最佳實(shí)踐
實(shí)踐 | 說(shuō)明 |
---|---|
? 使用 @RestControllerAdvice | 統(tǒng)一返回 JSON 格式 |
? 自定義 BusinessException | 區(qū)分業(yè)務(wù)異常與系統(tǒng)異常 |
? 記錄日志 | @Slf4j + log.error/warn |
? 敏感信息脫敏 | 不要將數(shù)據(jù)庫(kù)錯(cuò)誤、堆棧信息暴露給前端 |
? 合理分類(lèi)異常 | 優(yōu)先處理具體異常,最后是 Exception |
? 啟用 404 捕獲 | 配置 throw-exception-if-no-handler-found: true |
到此這篇關(guān)于Spring Boot全局異常處理的文章就介紹到這了,更多相關(guān)SpringBoot全局異常處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決Spring或SpringBoot開(kāi)啟事務(wù)以后無(wú)法返回自增主鍵的問(wèn)題
這篇文章主要介紹了解決Spring或SpringBoot開(kāi)啟事務(wù)以后無(wú)法返回自增主鍵的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07攔截JSP頁(yè)面,校驗(yàn)是否已登錄詳解及實(shí)現(xiàn)代碼
這篇文章主要介紹了攔截JSP頁(yè)面,校驗(yàn)是否已登錄詳解及實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-11-11rabbitmq的消息持久化處理開(kāi)啟,再關(guān)閉后,消費(fèi)者啟動(dòng)報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了rabbitmq的消息持久化處理開(kāi)啟,再關(guān)閉后,消費(fèi)者啟動(dòng)報(bào)錯(cuò)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11java使用zookeeper實(shí)現(xiàn)的分布式鎖示例
這篇文章主要介紹了java使用zookeeper實(shí)現(xiàn)的分布式鎖示例,需要的朋友可以參考下2014-05-05基于Comparator對(duì)象集合實(shí)現(xiàn)多個(gè)條件按照優(yōu)先級(jí)的比較
這篇文章主要介紹了基于Comparator對(duì)象集合實(shí)現(xiàn)多個(gè)條件按照優(yōu)先級(jí)的比較,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Maven中plugins和pluginManagement區(qū)別小結(jié)
pluginManagement是表示插件聲明,plugins就是直接引入一個(gè)plugin,本文主要介紹了Maven中plugins和pluginManagement區(qū)別小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06SpringBoot讀取外部的配置文件的代碼實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot讀取外部的配置文件的代碼實(shí)現(xiàn),文中通過(guò)代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-11-11