?<前文回顧>
<今日更新>
一、開篇整活兒
今兒個咱嘮嘮 Spring Boot 里頭的異常處理。這玩意兒吧,說大不大,說小不小,整好了是錦上添花,整不好就是火上澆油。你要是剛入門,那可得悠著點兒,別一上來就整得自己“翻車”了。
二、Java 中的異常是啥構型?
Java 里頭的異常,說白了就是程序運行時出的岔子。Java 的異常體系是“樹形結構”,最頂上是?Throwable,往下分成?Error?和?Exception。Error?是那種嚴重的、沒法兒處理的錯誤,比如說內存溢出啥的。Exception?是那種可以處理的異常,比如說空指針、數組越界啥的。
1.?Exception?的分類
Exception?又分成兩種:受檢異常和非受檢異常。
- 受檢異常:這種異常你得在代碼里頭顯式處理,要么?try-catch,要么?throws。比如說?IOException,你要是讀寫文件,那得處理這個異常。
- 非受檢異常:這種異常不用顯式處理,比如說?NullPointerException、ArrayIndexOutOfBoundsException,這些異常通常是代碼寫得不嚴謹導致的。
Java Code |
// 受檢異常示例 try { ????FileInputStream fis = new FileInputStream("file.txt"); } catch (IOException e) { ????e.printStackTrace(); } // 非受檢異常示例 String str = null; System.out.println(str.length()); // 這里會拋出 NullPointerException |
2. 異常之間的關系
Java 里頭的異常是“繼承關系”,子類異常可以捕獲父類異常。比如說,IOException?是?Exception?的子類,你要是?catch?了?Exception,那?IOException?也能被捕獲。
Java Code |
try { ????// 一些可能拋出異常的代碼 } catch (IOException e) { ????// 處理 IOException } catch (Exception e) { ????// 處理其他 Exception } |
這段代碼里頭,IOException?會被第一個?catch?捕獲,其他的?Exception?會被第二個?catch?捕獲。
三、Spring Boot 中的全局異常處理
Spring Boot 里頭有個?@ControllerAdvice?注解,專門用來做全局異常處理。你可以把它想象成一個“兜底的”,Controller 里頭沒處理的異常,它都能接住。
1. 全局異常處理示例
Java Code |
@ControllerAdvice public class GlobalExceptionHandler { ????@ExceptionHandler(Exception.class) ????public ResponseEntity<String> handleException(Exception e) { ????????return new ResponseEntity<>("出錯了:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); ????} } |
這段代碼里頭,@ControllerAdvice?是用來定義一個全局的異常處理類,@ExceptionHandler?是用來處理特定類型的異常的。ResponseEntity?是用來返回 HTTP 響應的,里頭可以帶上狀態碼和響應體。
2. 處理特定異常
你可以針對不同的異常,寫不同的處理方法。比如說,處理?NullPointerException?和?IllegalArgumentException。
Java Code |
@ControllerAdvice public class GlobalExceptionHandler { ????@ExceptionHandler(NullPointerException.class) ????public ResponseEntity<String> handleNullPointerException(NullPointerException e) { ????????return new ResponseEntity<>("空指針異常:" + e.getMessage(), HttpStatus.BAD_REQUEST); ????} ????@ExceptionHandler(IllegalArgumentException.class) ????public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) { ????????return new ResponseEntity<>("參數不合法:" + e.getMessage(), HttpStatus.BAD_REQUEST); ????} } |
這段代碼里頭,NullPointerException?和?IllegalArgumentException?會被分別處理,返回不同的錯誤信息。
四、自定義異常
有時候,Java 自帶的異常不夠用,你得自己整一個。自定義異常很簡單,繼承?Exception?或者?RuntimeException?就行。
1. 自定義異常示例
Java Code |
public class MyCustomException extends RuntimeException { ????public MyCustomException(String message) { ????????super(message); ????} } |
這段代碼里頭,MyCustomException?是自定義異常,繼承自?RuntimeException,所以它是非受檢異常。
2. 使用自定義異常
你可以在代碼里頭拋出這個自定義異常。
Java Code |
@RestController @RequestMapping("/api") public class MyController { ????@GetMapping("/test") ????public String test() { ????????throw new MyCustomException("這是我的自定義異常"); ????} } |
這段代碼里頭,test?方法會拋出?MyCustomException,然后被全局異常處理器捕獲。
3. 處理自定義異常
你可以在全局異常處理器里頭,專門處理這個自定義異常。
Java Code |
@ControllerAdvice public class GlobalExceptionHandler { ????@ExceptionHandler(MyCustomException.class) ????public ResponseEntity<String> handleMyCustomException(MyCustomException e) { ????????return new ResponseEntity<>("自定義異常:" + e.getMessage(), HttpStatus.BAD_REQUEST); ????} } |
這段代碼里頭,MyCustomException?會被專門處理,返回自定義的錯誤信息。
五、異常的 AOP 處理
AOP(面向切面編程)是 Spring 里頭的一個高級特性,可以用來在方法執行前后做一些額外的事情。異常處理也可以用 AOP 來做。
1. AOP 處理異常示例
Java Code |
@Aspect @Component public class ExceptionAspect { ????@AfterThrowing(pointcut = "execution(* com.example.demo.controller.*.*(..))", throwing = "e") ????public void handleException(Exception e) { ????????System.out.println("捕獲到異常:" + e.getMessage()); ????} } |
這段代碼里頭,@Aspect?是用來定義一個切面,@AfterThrowing?是用來在方法拋出異常后執行的。pointcut?是指定哪些方法需要被切面處理,throwing?是指定異常對象的變量名。
2. AOP 和全局異常處理的區別
AOP 和全局異常處理都可以用來處理異常,但它們的應用場景不一樣。全局異常處理是用來處理 Controller 里頭的異常,而 AOP 可以用來處理任何地方的異常,比如說 Service 層、DAO 層啥的。
六、Spring Boot 里頭的異常處理坑點
1. 異常處理的順序
Spring Boot 里頭,異常處理的順序很重要。你要是先?catch?了?Exception,那后面的?catch?就不起作用了。
Java Code |
@ControllerAdvice public class GlobalExceptionHandler { ????@ExceptionHandler(Exception.class) ????public ResponseEntity<String> handleException(Exception e) { ????????return new ResponseEntity<>("出錯了:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); ????} ????@ExceptionHandler(NullPointerException.class) ????public ResponseEntity<String> handleNullPointerException(NullPointerException e) { ????????return new ResponseEntity<>("空指針異常:" + e.getMessage(), HttpStatus.BAD_REQUEST); ????} } |
這段代碼里頭,NullPointerException?會被?handleException?捕獲,handleNullPointerException?就不起作用了。
2. 異常信息的暴露
Spring Boot 里頭,默認會把異常信息返回給客戶端。你要是覺得這樣不安全,可以把異常信息隱藏掉。
Java Code |
@ControllerAdvice public class GlobalExceptionHandler { ????@ExceptionHandler(Exception.class) ????public ResponseEntity<String> handleException(Exception e) { ????????return new ResponseEntity<>("出錯了", HttpStatus.INTERNAL_SERVER_ERROR); ????} } |
這段代碼里頭,異常信息被隱藏了,客戶端只能看到“出錯了”。
3. 日志記錄
異常處理里頭,日志記錄是個大事兒。你要是沒記日志,那出了問題就抓瞎了。
Java Code |
@ControllerAdvice public class GlobalExceptionHandler { ????private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); ????@ExceptionHandler(Exception.class) ????public ResponseEntity<String> handleException(Exception e) { ????????logger.error("捕獲到異常:", e); ????????return new ResponseEntity<>("出錯了", HttpStatus.INTERNAL_SERVER_ERROR); ????} } |
這段代碼里頭,異常信息被記錄到日志里頭了,方便以后排查問題。
專有名詞解釋
- Throwable:Java 中所有錯誤和異常的基類。
- Error:Java 中表示嚴重錯誤的類,通常無法恢復。
- Exception:Java 中表示可恢復異常的類。
- 受檢異常:必須在代碼中顯式處理的異常。
- 非受檢異常:不需要顯式處理的異常。
- ControllerAdvice:Spring 中用于定義全局異常處理類的注解。
- ExceptionHandler:Spring 中用于處理特定類型異常的注解。
- ResponseEntity:Spring 中用于封裝 HTTP 響應的類。
- AOP:面向切面編程,一種編程范式,用于在方法執行前后做一些額外的事情。
- Aspect:Spring 中用于定義切面的注解。
- AfterThrowing:Spring 中用于在方法拋出異常后執行的注解。
- Logger:用于記錄日志的工具類。