Spring?MVC全局異常處理實戰(zhàn)詳細指南
前言
在 Spring Boot 前后端分離項目開發(fā)中,異常處理是保障系統(tǒng)健壯性的關鍵環(huán)節(jié)。如果不對異常做統(tǒng)一處理,Controller 層拋出的異常會直接返回給前端雜亂的 500 錯誤頁面或原生異常信息,不僅前端無法友好解析,還可能泄露服務端核心代碼信息,影響系統(tǒng)安全性和用戶體驗。
而基于 Spring MVC 的@RestControllerAdvice + @ExceptionHandler實現(xiàn)的全局異常處理,正是解決這一問題的最優(yōu)解。它就像系統(tǒng)的 “統(tǒng)一兜底管家”,能攔截項目中 Controller 層拋出的所有異常,將其轉換為前端能統(tǒng)一解析的標準 JSON 響應格式,讓異常處理更規(guī)范、更優(yōu)雅。本文將從核心原理、代碼實現(xiàn)、執(zhí)行流程三個維度,詳解項目中全局異常處理的設計與落地。
一、全局異常處理核心思想:統(tǒng)一攔截,標準化響應
在講解具體實現(xiàn)前,先理解全局異常處理的核心設計思想,這是整個方案的基礎:
- 統(tǒng)一兜底:將項目中分散在各個 Controller、Service 層的異常捕獲邏輯抽離出來,集中在一個類中統(tǒng)一處理,避免重復的
try-catch代碼,讓業(yè)務代碼更專注于核心邏輯; - 精準匹配:針對不同類型的異常(如業(yè)務異常、SQL 異常、系統(tǒng)異常),配置對應的異常處理方法,實現(xiàn) “什么異常用什么方式處理”;
- 標準化響應:將所有異常都轉換為項目統(tǒng)一的 Result 響應格式,前端只需根據固定的格式解析(如錯誤碼、錯誤信息),無需適配不同的異常返回結果;
- 無侵入式:無需修改原有業(yè)務代碼,只需在需要拋異常的地方直接
throw異常即可,全局異常處理類會自動攔截,符合 “開閉原則”。
整個方案的核心只有一個類 ——GlobalExceptionHandler,通過兩個核心注解實現(xiàn)所有功能,代碼簡潔且功能強大,是 Spring MVC 提供的原生解決方案,無需引入任何第三方依賴。
二、核心注解解析:@RestControllerAdvice & @ExceptionHandler
全局異常處理的實現(xiàn)依賴 Spring MVC 的兩個核心注解,這兩個注解是整個方案的 “基石”,先搞懂它們的作用,才能理解后續(xù)的代碼實現(xiàn):
1. @RestControllerAdvice:聲明全局異常處理類
- 核心作用:標記當前類為全局異常處理類,并具備兩個關鍵特性:
- 全局生效:能攔截整個項目中所有
@RestController/@Controller標記的類拋出的異常; - 自動 JSON 序列化:處理異常的方法返回值會自動轉換為 JSON 格式,無需再添加
@ResponseBody注解,完美適配前后端分離的 JSON 交互場景。
- 全局生效:能攔截整個項目中所有
- 底層原理:基于 AOP(面向切面編程)實現(xiàn),相當于在所有 Controller 層方法上織入了異常捕獲的切面,當方法拋出異常時,自動觸發(fā)對應的異常處理邏輯。
2. @ExceptionHandler:指定處理的異常類型
- 核心作用:標注在方法上,指定該方法專門處理某一種 / 某一類異常,如
@ExceptionHandler(BaseException.class)表示該方法處理所有BaseException及其子類的異常; - 匹配規(guī)則:當系統(tǒng)拋出異常時,Spring MVC 會根據異常的類型,自動匹配到對應的
@ExceptionHandler方法,精準執(zhí)行處理邏輯; - 靈活度高:一個全局異常處理類中可以定義多個
@ExceptionHandler方法,分別處理不同類型的異常,實現(xiàn)異常的精細化處理。
三、核心實現(xiàn)類:GlobalExceptionHandler—— 系統(tǒng)的 “異常兜底管家”
項目中的全局異常處理核心類為GlobalExceptionHandler,它通過上述兩個核心注解,實現(xiàn)了業(yè)務異常和SQL 異常兩大核心場景的處理,同時可輕松擴展處理其他類型的異常。以下是完整的實戰(zhàn)代碼,并附帶詳細的代碼解釋,貼合企業(yè)級開發(fā)規(guī)范。
1. 先備基礎:統(tǒng)一響應類 Result & 基礎業(yè)務異常類 BaseException
在實現(xiàn)全局異常處理前,項目中需先定義統(tǒng)一響應類 Result和基礎業(yè)務異常類 BaseException,這是全局異常處理的基礎,保證異常響應的標準化和業(yè)務異常的統(tǒng)一管理。
(1)統(tǒng)一響應類 Result
項目中所有接口的返回結果都遵循該格式,異常處理也不例外,讓前端能統(tǒng)一解析響應數(shù)據:
package com.sky.result;
import lombok.Data;
/**
* 全局統(tǒng)一響應結果類
*/
@Data
public class Result<T> {
/**
* 響應碼:0表示成功,1/其他表示失敗
*/
private Integer code;
/**
* 響應信息:成功時為"成功",失敗時為錯誤信息
*/
private String msg;
/**
* 響應數(shù)據:成功時返回業(yè)務數(shù)據,失敗時為null
*/
private T data;
// 快速返回成功結果(無數(shù)據)
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.setCode(0);
result.setMsg("成功");
return result;
}
// 快速返回成功結果(帶數(shù)據)
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(0);
result.setMsg("成功");
result.setData(data);
return result;
}
// 快速返回失敗結果(自定義錯誤信息)
public static <T> Result<T> error(String msg) {
Result<T> result = new Result<>();
result.setCode(1);
result.setMsg(msg);
return result;
}
}
(2)基礎業(yè)務異常類 BaseException
項目中所有的業(yè)務異常都繼承自該類,實現(xiàn)業(yè)務異常的統(tǒng)一管理,如密碼錯誤、賬號鎖定、參數(shù)非法等:
package com.sky.exception;
/**
* 項目基礎業(yè)務異常類,所有業(yè)務異常都繼承此類
*/
public class BaseException extends RuntimeException {
// 構造方法,傳入錯誤信息
public BaseException(String message) {
super(message);
}
}
(3)業(yè)務異常子類示例(密碼錯誤)
可根據業(yè)務需求,定義多個 BaseException 的子類,實現(xiàn)更精細化的業(yè)務異常管理:
package com.sky.exception;
/**
* 密碼錯誤異常
*/
public class PasswordErrorException extends BaseException {
public PasswordErrorException(String message) {
super(message);
}
}
2. 全局異常處理核心代碼
package com.sky.handler;
import com.sky.constant.MessageConstant;
import com.sky.exception.BaseException;
import com.sky.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.sql.SQLIntegrityConstraintViolationException;
/**
* 全局異常處理類,統(tǒng)一攔截并處理項目中所有的異常
*/
@RestControllerAdvice // 聲明全局異常處理類,返回值自動轉JSON
@Slf4j // 日志注解,記錄異常信息
public class GlobalExceptionHandler {
/**
* 處理業(yè)務異常:捕獲BaseException及其所有子類異常
* 適用場景:密碼錯誤、賬號鎖定、參數(shù)非法等自定義業(yè)務異常
*/
@ExceptionHandler(BaseException.class)
public Result<String> handleBaseException(BaseException ex) {
// 記錄異常信息,便于問題排查
log.error("業(yè)務異常:{}", ex.getMessage(), ex);
// 將業(yè)務異常的錯誤信息封裝為標準Result格式,返回給前端
return Result.error(ex.getMessage());
}
/**
* 處理SQL唯一約束異常:捕獲用戶名/手機號等唯一字段重復的異常
* 適用場景:新增員工/用戶時,用戶名重復;新增手機號時,號碼重復等
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public Result<String> handleSQLIntegrityException(SQLIntegrityConstraintViolationException ex) {
log.error("SQL約束異常:{}", ex.getMessage(), ex);
// 異常信息示例:Duplicate entry 'zhangsan' for key 'idx_employee_username'
String message = ex.getMessage();
// 解析異常信息,提取重復的字段值,返回友好的錯誤信息
if (message.contains("Duplicate entry")) {
String[] split = message.split(" ");
String duplicateValue = split[2].replace("'", ""); // 去除單引號,如zhangsan
String msg = duplicateValue + "已存在,請勿重復添加";
return Result.error(msg);
}
// 其他SQL約束異常,返回通用錯誤信息
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
/**
* 處理系統(tǒng)通用異常:捕獲所有未被上述方法匹配的異常(兜底處理)
* 適用場景:空指針、數(shù)組越界、網絡異常等系統(tǒng)級異常
*/
@ExceptionHandler(Exception.class)
public Result<String> handleGlobalException(Exception ex) {
log.error("系統(tǒng)異常:{}", ex.getMessage(), ex);
// 系統(tǒng)異常不暴露具體錯誤信息給前端,返回通用友好提示
return Result.error(MessageConstant.SYSTEM_ERROR);
}
}
3. 關鍵代碼詳解
(1)業(yè)務異常處理方法handleBaseException
- 匹配范圍:處理所有
BaseException及其子類(如PasswordErrorException、AccountLockedException),覆蓋項目中所有自定義的業(yè)務異常; - 處理邏輯:記錄異常日志→將異常的自定義錯誤信息(如 “密碼錯誤”)封裝為 Result 格式返回;
- 設計優(yōu)勢:業(yè)務層只需直接拋出
new BaseException("錯誤信息"),無需關心異常如何處理,全局異常處理類會自動攔截,讓業(yè)務代碼更簡潔。
(2)SQL 唯一約束異常處理方法handleSQLIntegrityException
- 匹配場景:專門處理數(shù)據庫唯一索引約束異常(如用戶名、手機號重復),該異常由數(shù)據庫直接拋出,類型為
SQLIntegrityConstraintViolationException; - 友好解析:對數(shù)據庫原生的異常信息進行解析,提取重復的字段值,返回如 “zhangsan 已存在,請勿重復添加” 的友好信息,而非原生的雜亂 SQL 異常信息;
- 用戶體驗:前端收到友好的錯誤信息后,可直接彈窗提示用戶,提升交互體驗。
(3)系統(tǒng)通用異常處理方法handleGlobalException
- 兜底處理:匹配
Exception.class,即所有未被上述方法處理的異常都會被該方法捕獲,避免有異常未被攔截而返回原生錯誤; - 安全設計:系統(tǒng)級異常(如空指針、數(shù)組越界)的具體信息不暴露給前端,僅返回 “系統(tǒng)繁忙,請稍后重試” 等通用友好提示,防止泄露服務端核心代碼信息;
- 日志記錄:詳細記錄異常的堆棧信息,便于開發(fā)人員排查問題,實現(xiàn) “前端友好,后端可查”。
四、實戰(zhàn)使用:業(yè)務層直接拋異常,無需任何處理
完成全局異常處理類的編寫后,在項目中使用變得極其簡單 ——業(yè)務層只需根據業(yè)務邏輯直接拋出異常,無需編寫任何 try-catch 代碼,全局異常處理類會自動攔截并處理,返回標準化的 JSON 響應。
1. 業(yè)務層拋業(yè)務異常示例(員工登錄密碼錯誤)
在員工登錄的 Service 層中,若校驗密碼錯誤,直接拋出PasswordErrorException(繼承自 BaseException):
package com.sky.service.impl;
import com.sky.constant.MessageConstant;
import com.sky.entity.Employee;
import com.sky.exception.PasswordErrorException;
import com.sky.mapper.EmployeeMapper;
import com.sky.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
@Override
public Employee login(String username, String password) {
// 1. 根據用戶名查詢員工
Employee employee = employeeMapper.selectByUsername(username);
// 2. 用戶名不存在,拋業(yè)務異常
if (employee == null) {
throw new BaseException(MessageConstant.USERNAME_NOT_FOUND);
}
// 3. 密碼錯誤,拋密碼錯誤異常
if (!password.equals(employee.getPassword())) {
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}
// 4. 賬號鎖定,拋業(yè)務異常
if (employee.getStatus() == 0) {
throw new BaseException(MessageConstant.ACCOUNT_LOCKED);
}
// 5. 登錄成功,返回員工信息
return employee;
}
}
2. 前端收到的標準化異常響應
當密碼錯誤時,全局異常處理類會自動攔截PasswordErrorException,返回以下 JSON 響應,前端可直接解析msg字段提示用戶:
{
"code": 1,
"msg": "密碼錯誤,請重新輸入",
"data": null
}3. SQL 唯一約束異常示例(新增員工用戶名重復)
當新增員工時,若用戶名已存在,數(shù)據庫會拋出SQLIntegrityConstraintViolationException,全局異常處理類會解析異常信息,返回友好的響應:
{
"code": 1,
"msg": "zhangsan已存在,請勿重復添加",
"data": null
}4. 系統(tǒng)通用異常示例(空指針異常)
當項目中出現(xiàn)空指針異常時,全局異常處理類會捕獲該異常,返回通用友好提示,同時在服務端日志中記錄詳細的堆棧信息:
{
"code": 1,
"msg": "系統(tǒng)繁忙,請稍后重試",
"data": null
}五、完整執(zhí)行流程:從拋異常到前端收到響應
全局異常處理的整個執(zhí)行流程完全自動化,對開發(fā)者無感知,具體步驟如下(以密碼錯誤為例):
- 業(yè)務層拋異常:員工登錄時,Service 層校驗密碼錯誤,拋出
PasswordErrorException("密碼錯誤,請重新輸入"); - 異常向上冒泡:該異常會從 Service 層向上拋到 Controller 層,Controller 層未編寫 try-catch 代碼,異常繼續(xù)向上拋;
- 全局攔截異常:Spring MVC 的核心調度器
DispatcherServlet捕獲到異常,發(fā)現(xiàn)項目中有@RestControllerAdvice標記的GlobalExceptionHandler類; - 匹配異常處理方法:根據異常類型
PasswordErrorException(繼承自 BaseException),自動匹配到handleBaseException方法; - 處理異常并返回:執(zhí)行
handleBaseException方法,記錄異常日志,將異常信息封裝為標準 Result 格式的 JSON 響應; - 前端接收響應:前端收到包含錯誤碼和錯誤信息的 JSON 數(shù)據,根據
code=1判斷為失敗,解析msg字段彈窗提示用戶。
整個流程一氣呵成,無需開發(fā)者介入,實現(xiàn)了 “拋異常即處理” 的優(yōu)雅效果。
六、擴展場景:輕松適配更多異常類型
項目中的GlobalExceptionHandler具備極強的可擴展性,若需要處理其他類型的異常,只需新增一個 @ExceptionHandler 方法,指定對應的異常類型即可,無需修改原有代碼。以下是幾個常見的擴展場景示例:
1. 處理請求參數(shù)校驗異常(@Validated 校驗失?。?/h3>
項目中常用@Validated做請求參數(shù)的后端校驗(如非空、長度限制),校驗失敗會拋出MethodArgumentNotValidException,新增處理方法:
/**
* 處理請求參數(shù)校驗異常:@Validated校驗失敗時觸發(fā)
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> handleValidException(MethodArgumentNotValidException ex) {
log.error("參數(shù)校驗異常:{}", ex.getMessage(), ex);
// 獲取第一個校驗失敗的錯誤信息
String msg = ex.getBindingResult().getFieldError().getDefaultMessage();
return Result.error(msg);
}
2. 處理跨域異常
若項目中跨域配置不當,會拋出跨域異常,新增處理方法:
/**
* 處理跨域異常
*/
@ExceptionHandler(NoHandlerFoundException.class)
public Result<String> handleNoHandlerFoundException(NoHandlerFoundException ex) {
log.error("跨域異常:{}", ex.getMessage(), ex);
return Result.error(MessageConstant.CROSS_DOMAIN_ERROR);
}
3. 處理 Redis 連接異常
若項目中使用 Redis,Redis 連接失敗會拋出異常,新增處理方法:
/**
* 處理Redis連接異常
*/
@ExceptionHandler(RedisConnectionFailureException.class)
public Result<String> handleRedisException(RedisConnectionFailureException ex) {
log.error("Redis連接異常:{}", ex.getMessage(), ex);
return Result.error(MessageConstant.REDIS_CONNECT_ERROR);
}
七、方案優(yōu)勢:為什么說這是企業(yè)級項目的最佳實踐?
這套基于@RestControllerAdvice + @ExceptionHandler的全局異常處理方案,是 Spring MVC 項目中異常處理的企業(yè)級最佳實踐,相比傳統(tǒng)的分散式try-catch,具有以下核心優(yōu)勢:
1. 消除重復代碼,提升代碼整潔度
將分散在各個 Controller、Service 層的try-catch代碼全部抽離,集中在一個類中統(tǒng)一處理,業(yè)務代碼中只需專注于核心邏輯,無需關心異常處理,代碼更簡潔、更優(yōu)雅。
2. 標準化響應,降低前后端溝通成本
所有異常都返回統(tǒng)一的 Result 格式,前端只需根據固定的code和msg字段解析響應,無需適配不同的異常返回結果,大幅降低前后端的溝通和開發(fā)成本。
3. 精細化處理,兼顧友好性和可排查性
- 對前端友好:業(yè)務異常返回自定義的友好錯誤信息,系統(tǒng)異常返回通用提示,避免前端收到雜亂的原生異常信息;
- 對后端友好:詳細記錄所有異常的日志(包括堆棧信息),開發(fā)人員可通過日志快速定位問題,實現(xiàn) “前端友好,后端可查”。
4. 無侵入式設計,符合開閉原則
無需修改原有業(yè)務代碼,只需在需要拋異常的地方直接throw即可,新增異常類型時,只需新增對應的@ExceptionHandler方法,無需修改原有處理邏輯,完全符合 “對擴展開放,對修改關閉” 的開閉原則。
5. 全局生效,全覆蓋無遺漏
能攔截項目中所有 Controller 層拋出的異常,包括業(yè)務異常、SQL 異常、系統(tǒng)異常等,實現(xiàn)異常處理的全覆蓋,避免有異常未被攔截而返回原生 500 錯誤。
6. 可擴展性強,適配所有業(yè)務場景
可根據項目需求,輕松擴展處理任意類型的異常,只需新增方法即可,適配從簡單的業(yè)務異常到復雜的系統(tǒng)異常的所有場景。
八、生產環(huán)境優(yōu)化建議
以上實現(xiàn)為項目基礎版的全局異常處理方案,在生產環(huán)境中,可根據實際需求進行以下優(yōu)化,讓方案更健壯、更安全:
- 自定義錯誤碼體系:擴展 Result 類,增加自定義錯誤碼(如 1001 = 密碼錯誤、1002 = 賬號鎖定、2001 = 用戶名重復),讓前端能根據錯誤碼做更精細化的處理(如不同的錯誤提示樣式);
- 異常分類管理:將異常分為業(yè)務異常、系統(tǒng)異常、第三方異常(如 Redis、MQ、接口調用異常),分別進行處理,返回不同的錯誤碼和提示信息;
- 統(tǒng)一日志格式:規(guī)范異常日志的記錄格式,包含異常類型、錯誤信息、請求 URL、請求 IP、用戶 ID、堆棧信息等,便于生產環(huán)境的日志分析和問題排查;
- 添加異常監(jiān)控:結合監(jiān)控平臺(如 Prometheus + Grafana、ELK),對系統(tǒng)異常進行監(jiān)控和告警,當系統(tǒng)異常數(shù)量達到閾值時,自動發(fā)送告警通知(如郵件、釘釘、企業(yè)微信);
- 自定義異常頁面:若項目包含非前后端分離的頁面,可通過
@ControllerAdvice(不帶 Rest)配合ModelAndView,返回自定義的異常頁面; - 限制異常日志打印級別:生產環(huán)境中,業(yè)務異??纱蛴?code>INFO級別日志,系統(tǒng)異常打印
ERROR級別日志,避免日志過多導致磁盤溢出; - 敏感信息脫敏:對異常信息中的敏感數(shù)據(如密碼、手機號、身份證號)進行脫敏處理,防止敏感信息泄露在日志中。
九、總結
本文講解的全局異常處理方案,是Spring MVC 原生特性在企業(yè)級項目中的經典落地實踐,核心是通過@RestControllerAdvice和@ExceptionHandler兩個注解,實現(xiàn)了異常的統(tǒng)一攔截、精準匹配、標準化響應。
這套方案的核心價值,不僅在于解決了前端異常解析的問題,更在于讓異常處理與業(yè)務邏輯完全解耦—— 開發(fā)者無需在業(yè)務層編寫任何異常處理代碼,只需專注于業(yè)務邏輯的實現(xiàn),異常的捕獲和處理全部由全局異常處理類自動完成。同時,其無侵入式的設計和極強的可擴展性,讓它能輕松適配項目的所有業(yè)務場景,從簡單的業(yè)務異常到復雜的系統(tǒng)異常,都能實現(xiàn)精細化處理。
在實際項目開發(fā)中,全局異常處理是必備的基礎功能,它是保障系統(tǒng)健壯性、提升用戶體驗、降低開發(fā)成本的關鍵環(huán)節(jié)。掌握這套方案,不僅能解決項目中的異常處理問題,更能理解 Spring MVC 的 AOP 思想和 “集中式處理通用邏輯” 的設計理念,讓我們的代碼更規(guī)范、更優(yōu)雅、更具可維護性。
優(yōu)秀的項目架構,不僅能實現(xiàn)業(yè)務功能,更能在細節(jié)處做到極致 —— 而全局異常處理,正是項目架構細節(jié)中 “優(yōu)雅” 的體現(xiàn)。
到此這篇關于Spring MVC全局異常處理的文章就介紹到這了,更多相關Spring MVC全局異常處理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Maven方式構建SpringBoot項目的實現(xiàn)步驟(圖文)
Maven是一個強大的項目管理工具,可以幫助您輕松地構建和管理Spring Boot應用程序,本文主要介紹了Maven方式構建SpringBoot項目的實現(xiàn)步驟,具有一定的參考價值,感興趣的可以了解一下2023-09-09
Java獲取當前系統(tǒng)事件System.currentTimeMillis()方法
下面小編就為大家?guī)硪黄狫ava獲取當前系統(tǒng)事件System.currentTimeMillis()方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06

