@ControllerAdvice之全局異常處理過程
背景
在業(yè)務(wù)邏輯中存在大量重復(fù)的 try-catch 代碼塊(且沒有全局異常處理機(jī)制)會帶來以下顯著問題:
- 每個方法都需要手動編寫 try-catch,導(dǎo)致大量重復(fù)代碼,違反 DRY(Don’t Repeat Yourself)原則。
- 如果異常處理邏輯需要調(diào)整(例如日志格式、錯誤碼規(guī)范),必須逐個修改所有 try-catch 塊,容易遺漏或出錯。
public void processOrder() {
try {
// 業(yè)務(wù)邏輯
} catch (OrderException e) {
log.error("訂單處理失敗: {}", e.getMessage());
}
}
public void refundOrder() {
try {
// 業(yè)務(wù)邏輯
} catch (RefundException e) {
log.error("退款失敗: {}", e.getMessage());
}
}
java中的異常處理方式
1、就地解決
即用try-catch塊包裹住容易發(fā)生異常的代碼片段,這樣當(dāng)該代碼片段真正發(fā)生異常后,就會立即被catch塊捕捉并在catch塊中處理,從而使代碼繼續(xù)往下執(zhí)行,不影響其他代碼的執(zhí)行。
2、向上拋出
如果不想立馬就地處理,可以選擇將該異常向上拋出,讓方法的調(diào)用者處理。若方法的調(diào)用者也不行處理,同樣可以繼續(xù)向上拋出該異常,以此類推,直到將該異常拋給JVM處理??墒荍VM懶啊,一看你們該處理的都不處理是吧,好,我也不處理,我還要把你們方法的調(diào)用過程給曬出來,結(jié)果就可以在控制臺看到方法調(diào)用的堆棧信息了。
為什么要使用全局異常處理
總是用try-catch塊包裹代碼塊也不好,影響性能不說代碼看著不是很美觀,所以我們選擇用第二種——向上拋出的方式。向上拋出沒問題,但總要有一個地方處理該異常,在web系統(tǒng)當(dāng)中還應(yīng)該將該異常以一個用戶可以接受的方式返回給前端,不但在接口對接的時候讓前端小姐姐知道是我們后臺接口出現(xiàn)了問題,不至于摸不著頭腦;而且我們后端開發(fā)人員也能根據(jù)接口返回的結(jié)果快速的知道到底是哪里出現(xiàn)了問題,才能快速解決問題。
全局異常處理
Spring在3.2版本增加了一個注解@ControllerAdvice,可以與@ExceptionHandler、@InitBinder、@ModelAttribute 等注解注解配套使用。
不過跟異常處理相關(guān)的只有注解@ExceptionHandler(該注解是springmvc中的一個注解)
該注解的作用:它通常用在控制器(Controller)類的方法上,以指定該方法用于處理特定類型的異常。當(dāng)控制器中的其他方法拋出該異常時,Spring 會自動調(diào)用帶有 @ExceptionHandler 注解的方法來處理該異常。
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
// 這是一個簡單的控制器類
@RestController
public class MyController {
// 一個可能會拋出異常的方法
public String doSomething() {
// 假設(shè)這里有一些邏輯,可能會拋出 IllegalArgumentException
if (true) { // 這里只是為了演示,實際上應(yīng)該有具體的邏輯判斷
throw new IllegalArgumentException("Invalid argument provided");
}
return "Success";
}
// 使用 @ExceptionHandler 來處理 IllegalArgumentException
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
// 創(chuàng)建一個包含錯誤信息的響應(yīng)實體
return new ResponseEntity<>("Invalid argument: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
在上面的例子中,doSomething 方法可能會拋出一個 IllegalArgumentException。當(dāng)這個異常被拋出時,Spring 會自動調(diào)用 handleIllegalArgumentException 方法來處理它,并返回一個包含錯誤信息的 ResponseEntity 對象,其 HTTP 狀態(tài)碼為 400 Bad Request。
但是,這樣一來,就必須在每一個Controller類都定義一套這樣的異常處理方法,因為異常可以是各種各樣。這樣一來,就會造成大量的冗余代碼,而且若需要新增一種異常的處理邏輯,就必須修改所有Controller類了,很不優(yōu)雅。
當(dāng)然你可能會說,那就定義個類似BaseController的基類,這樣總行了吧。
這種做法雖然沒錯,但仍不盡善盡美,因為這樣的代碼有一定的侵入性和耦合性。簡簡單單的Controller,我為啥非得繼承這樣一個類呢,萬一已經(jīng)繼承其他基類了呢。大家都知道Java只能繼承一個類。
那有沒有一種方案,既不需要跟Controller耦合,也可以將定義的 異常處理器 應(yīng)用到所有控制器呢?所以注解@ControllerAdvice出現(xiàn)了,簡單的說,該注解可以把異常處理器應(yīng)用到所有控制器,而不是單個控制器。借助該注解,我們可以實現(xiàn):在獨立的某個地方,比如單獨一個類,定義一套對各種異常的處理機(jī)制,然后在類的簽名加上注解@ControllerAdvice,統(tǒng)一對 不同階段的、不同異常 進(jìn)行處理。這就是統(tǒng)一異常處理的原理。
@ControllerAdvice使用
1、創(chuàng)建 MyControllerAdvice.java
如下:
package com.sam.demo.controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* controller 增強(qiáng)器
*
* @author sam
* @since 2017/7/17
*/
@ControllerAdvice
public class MyControllerAdvice {
/**
* 全局異常捕捉處理
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = Exception.class)
public Map errorHandler(Exception ex) {
Map map = new HashMap();
map.put("code", 100);
map.put("msg", ex.getMessage());
return map;
}
/**
* 攔截捕捉自定義異常 MyException.class
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = MyException.class)
public Map myErrorHandler(MyException ex) {
Map map = new HashMap();
map.put("code", ex.getCode());
map.put("msg", ex.getMsg());
return map;
}
}
2、controller中拋出異常進(jìn)行測試
@RequestMapping("/home")
public String home() throws Exception {
// throw new Exception("Sam 錯誤");
throw new MyException("101", "Sam 錯誤");
}
啟動應(yīng)用,訪問:http://localhost:8080/home ,正常顯示以下json內(nèi)容,證明自定義異常已經(jīng)成功被攔截。
{"msg":"Sam 錯誤","code":"101"}
@ControllerAdvice的原理
1、組件掃描與注冊
- 注解標(biāo)記:當(dāng)類被 @ControllerAdvice 標(biāo)注時,Spring 會在啟動時通過組件掃描(Component Scan)發(fā)現(xiàn)該類。
- Bean 注冊:將該類注冊為特殊的全局組件,其優(yōu)先級高于普通 Controller,但低于具體的 Controller 方法。
2、異常處理流程
當(dāng) Controller 方法拋出異常時,Spring MVC 的處理流程如下:
- 異常拋出:Controller 方法執(zhí)行中拋出異常(如 throw new ServiceException())。
- 異常傳播:異常被 Spring 的 DispatcherServlet 捕獲。
- 查找處理器:Spring 遍歷所有 @ControllerAdvice 類中的 @ExceptionHandler 方法,尋找與異常類型匹配的方法。
執(zhí)行處理:調(diào)用匹配的 @ExceptionHandler 方法生成響應(yīng)(如返回 JSON 錯誤信息)。 - 返回響應(yīng):將處理結(jié)果返回客戶端,中斷原始請求流程。
@ControllerAdvice
public class GlobalExceptionHandler {
// 處理特定異常
@ExceptionHandler(ServiceException.class)
@ResponseBody
public ResponseResult<Void> handleServiceException(ServiceException e) {
return ResponseResult.fail(e.getCode(), e.getMessage());
}
// 處理所有未捕獲的異常
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseResult<Void> handleException(Exception e) {
return ResponseResult.fail(500, "系統(tǒng)繁忙");
}
}
3、優(yōu)先級規(guī)則
- 精確匹配優(yōu)先:@ExceptionHandler 方法定義的異常類型越具體,優(yōu)先級越高。
- Controller 內(nèi)優(yōu)先:若 Controller 內(nèi)部定義了 @ExceptionHandler,則優(yōu)先于全局 @ControllerAdvice。
- 多個 @ControllerAdvice 類:通過 @Order 注解指定優(yōu)先級,數(shù)值越小優(yōu)先級越高。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot 中異步任務(wù),定時任務(wù),郵件任務(wù)詳解
這篇文章主要介紹了springboot 與異步任務(wù),定時任務(wù),郵件任務(wù),本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09
利用Java工具類Hutool實現(xiàn)驗證碼校驗功能
這篇文章主要介紹了利用Java工具類Hutool實現(xiàn)驗證碼校驗功能,利用Hutool實現(xiàn)驗證碼校驗,校驗的Servlet與今天的第一篇是一樣的,唯一就是驗證碼的生成是不一樣的,利用Hutool生成驗證碼更快捷.需要的朋友可以參考下2022-10-10
初學(xué)者易上手的SSH-struts2 01環(huán)境搭建(圖文教程)
下面小編就為大家?guī)硪黄鯇W(xué)者易上手的SSH-struts2 01環(huán)境搭建(圖文教程)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10
spring security獲取用戶信息為null或者串值的解決
這篇文章主要介紹了spring security獲取用戶信息為null或者串值的解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
java ConcurrentHashMap鎖分段技術(shù)及原理詳解
這篇文章主要介紹了java ConcurrentHashMap鎖分段技術(shù)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-07-07
Spring事務(wù)Transaction配置的五種注入方式詳解
這篇文章主要介紹了Spring事務(wù)Transaction配置的五種注入方式詳解,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04

