@ControllerAdvice之全局異常處理過程
背景
在業(yè)務(wù)邏輯中存在大量重復(fù)的 try-catch 代碼塊(且沒有全局異常處理機(jī)制)會(huì)帶來以下顯著問題:
- 每個(gè)方法都需要手動(dòng)編寫 try-catch,導(dǎo)致大量重復(fù)代碼,違反 DRY(Don’t Repeat Yourself)原則。
- 如果異常處理邏輯需要調(diào)整(例如日志格式、錯(cuò)誤碼規(guī)范),必須逐個(gè)修改所有 try-catch 塊,容易遺漏或出錯(cuò)。
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ā)生異常后,就會(huì)立即被catch塊捕捉并在catch塊中處理,從而使代碼繼續(xù)往下執(zhí)行,不影響其他代碼的執(zhí)行。
2、向上拋出
如果不想立馬就地處理,可以選擇將該異常向上拋出,讓方法的調(diào)用者處理。若方法的調(diào)用者也不行處理,同樣可以繼續(xù)向上拋出該異常,以此類推,直到將該異常拋給JVM處理??墒荍VM懶啊,一看你們?cè)撎幚淼亩疾惶幚硎前?,好,我也不處理,我還要把你們方法的調(diào)用過程給曬出來,結(jié)果就可以在控制臺(tái)看到方法調(diào)用的堆棧信息了。
為什么要使用全局異常處理
總是用try-catch塊包裹代碼塊也不好,影響性能不說代碼看著不是很美觀,所以我們選擇用第二種——向上拋出的方式。向上拋出沒問題,但總要有一個(gè)地方處理該異常,在web系統(tǒng)當(dāng)中還應(yīng)該將該異常以一個(gè)用戶可以接受的方式返回給前端,不但在接口對(duì)接的時(shí)候讓前端小姐姐知道是我們后臺(tái)接口出現(xiàn)了問題,不至于摸不著頭腦;而且我們后端開發(fā)人員也能根據(jù)接口返回的結(jié)果快速的知道到底是哪里出現(xiàn)了問題,才能快速解決問題。
全局異常處理
Spring在3.2版本增加了一個(gè)注解@ControllerAdvice,可以與@ExceptionHandler、@InitBinder、@ModelAttribute 等注解注解配套使用。
不過跟異常處理相關(guān)的只有注解@ExceptionHandler(該注解是springmvc中的一個(gè)注解)
該注解的作用:它通常用在控制器(Controller)類的方法上,以指定該方法用于處理特定類型的異常。當(dāng)控制器中的其他方法拋出該異常時(shí),Spring 會(huì)自動(dòng)調(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;
// 這是一個(gè)簡(jiǎn)單的控制器類
@RestController
public class MyController {
// 一個(gè)可能會(huì)拋出異常的方法
public String doSomething() {
// 假設(shè)這里有一些邏輯,可能會(huì)拋出 IllegalArgumentException
if (true) { // 這里只是為了演示,實(shí)際上應(yīng)該有具體的邏輯判斷
throw new IllegalArgumentException("Invalid argument provided");
}
return "Success";
}
// 使用 @ExceptionHandler 來處理 IllegalArgumentException
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
// 創(chuàng)建一個(gè)包含錯(cuò)誤信息的響應(yīng)實(shí)體
return new ResponseEntity<>("Invalid argument: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
在上面的例子中,doSomething 方法可能會(huì)拋出一個(gè) IllegalArgumentException。當(dāng)這個(gè)異常被拋出時(shí),Spring 會(huì)自動(dòng)調(diào)用 handleIllegalArgumentException 方法來處理它,并返回一個(gè)包含錯(cuò)誤信息的 ResponseEntity 對(duì)象,其 HTTP 狀態(tài)碼為 400 Bad Request。
但是,這樣一來,就必須在每一個(gè)Controller類都定義一套這樣的異常處理方法,因?yàn)楫惓?梢允歉鞣N各樣。這樣一來,就會(huì)造成大量的冗余代碼,而且若需要新增一種異常的處理邏輯,就必須修改所有Controller類了,很不優(yōu)雅。
當(dāng)然你可能會(huì)說,那就定義個(gè)類似BaseController的基類,這樣總行了吧。
這種做法雖然沒錯(cuò),但仍不盡善盡美,因?yàn)檫@樣的代碼有一定的侵入性和耦合性。簡(jiǎn)簡(jiǎn)單單的Controller,我為啥非得繼承這樣一個(gè)類呢,萬一已經(jīng)繼承其他基類了呢。大家都知道Java只能繼承一個(gè)類。
那有沒有一種方案,既不需要跟Controller耦合,也可以將定義的 異常處理器 應(yīng)用到所有控制器呢?所以注解@ControllerAdvice出現(xiàn)了,簡(jiǎn)單的說,該注解可以把異常處理器應(yīng)用到所有控制器,而不是單個(gè)控制器。借助該注解,我們可以實(shí)現(xiàn):在獨(dú)立的某個(gè)地方,比如單獨(dú)一個(gè)類,定義一套對(duì)各種異常的處理機(jī)制,然后在類的簽名加上注解@ControllerAdvice,統(tǒng)一對(duì) 不同階段的、不同異常 進(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)行測(cè)試
@RequestMapping("/home")
public String home() throws Exception {
// throw new Exception("Sam 錯(cuò)誤");
throw new MyException("101", "Sam 錯(cuò)誤");
}
啟動(dòng)應(yīng)用,訪問:http://localhost:8080/home ,正常顯示以下json內(nèi)容,證明自定義異常已經(jīng)成功被攔截。
{"msg":"Sam 錯(cuò)誤","code":"101"}
@ControllerAdvice的原理
1、組件掃描與注冊(cè)
- 注解標(biāo)記:當(dāng)類被 @ControllerAdvice 標(biāo)注時(shí),Spring 會(huì)在啟動(dòng)時(shí)通過組件掃描(Component Scan)發(fā)現(xiàn)該類。
- Bean 注冊(cè):將該類注冊(cè)為特殊的全局組件,其優(yōu)先級(jí)高于普通 Controller,但低于具體的 Controller 方法。
2、異常處理流程
當(dāng) Controller 方法拋出異常時(shí),Spring MVC 的處理流程如下:
- 異常拋出:Controller 方法執(zhí)行中拋出異常(如 throw new ServiceException())。
- 異常傳播:異常被 Spring 的 DispatcherServlet 捕獲。
- 查找處理器:Spring 遍歷所有 @ControllerAdvice 類中的 @ExceptionHandler 方法,尋找與異常類型匹配的方法。
執(zhí)行處理:調(diào)用匹配的 @ExceptionHandler 方法生成響應(yīng)(如返回 JSON 錯(cuò)誤信息)。 - 返回響應(yīng):將處理結(jié)果返回客戶端,中斷原始請(qǐng)求流程。
@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)先級(jí)規(guī)則
- 精確匹配優(yōu)先:@ExceptionHandler 方法定義的異常類型越具體,優(yōu)先級(jí)越高。
- Controller 內(nèi)優(yōu)先:若 Controller 內(nèi)部定義了 @ExceptionHandler,則優(yōu)先于全局 @ControllerAdvice。
- 多個(gè) @ControllerAdvice 類:通過 @Order 注解指定優(yōu)先級(jí),數(shù)值越小優(yōu)先級(jí)越高。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Spring AOP 攔截器的基本實(shí)現(xiàn)
本篇文章主要介紹了詳解Spring AOP 攔截器的基本實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03
Java 中的CharArrayReader 介紹_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
CharArrayReader 是字符數(shù)組輸入流。它和ByteArrayInputStream類似,只不過ByteArrayInputStream是字節(jié)數(shù)組輸入流,而CharArray是字符數(shù)組輸入流。CharArrayReader 是用于讀取字符數(shù)組,它繼承于Reader2017-05-05
解決spring-boot-maven-plugin報(bào)紅的問題
這篇文章主要給大家介紹一下如何解決spring-boot-maven-plugin報(bào)紅的問題,文中通過圖文講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2023-08-08
Java使用OpenOffice將office文件轉(zhuǎn)換為PDF的示例方法
OpenOffice是一個(gè)開源的辦公套件,它包含了文檔處理、電子表格、演示文稿以及繪圖等多種功能,類似于Microsoft Office,本文將給大家介紹Java使用OpenOffice將office文件轉(zhuǎn)換為PDF的示例方法,需要的朋友可以參考下2024-09-09
springboot訪問不存在的URL時(shí)的處理方法
在前后端分離的模式下,當(dāng)Spring Boot應(yīng)用接收到一個(gè)不存在的URL請(qǐng)求時(shí),通常希望返回一個(gè)固定的JSON字符串作為響應(yīng),以便前端能夠據(jù)此進(jìn)行相應(yīng)的處理,本文給大家介紹了springboot訪問不存在的URL時(shí)的處理方法,需要的朋友可以參考下2024-12-12
Java CountDownLatch應(yīng)用場(chǎng)景代碼實(shí)例
這篇文章主要介紹了Java CountDownLatch應(yīng)用場(chǎng)景代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
Springboot如何連接遠(yuǎn)程服務(wù)器上的數(shù)據(jù)庫實(shí)踐
本文主要介紹了Springboot如何連接遠(yuǎn)程服務(wù)器上的數(shù)據(jù)庫實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
JDK1.6“新“特性Instrumentation之JavaAgent(推薦)
這篇文章主要介紹了JDK1.6“新“特性Instrumentation之JavaAgent,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08

