關注微信公眾號【
Java之言】,更多干貨文章和學習資料,助你放棄編程之路!
文章目錄
- 一、為何要處理全域例外?
- 二、開發環境
- 三、添加依賴
- 四、自定義例外錯誤類
- 4.1 自定義例外基礎介面類
- 4.2 通用例外資訊列舉類
- 4.3 業務例外資訊列舉類
- 4.4 自定義業務例外類
- 五、介面回傳統一格式
- 六、全域例外處理
- 七、測驗
- 7.1 輔助類
- 7.2 測驗結果
一、為何要處理全域例外?
在平常專案開發程序中,程式難免會出現運行時例外,或者業務例外,難道要針對每一處可能出現的例外進行撰寫代碼進行處理?或者直接不處理例外,將一大屏堆滿英文的例外資訊顯示給用戶?那用戶體驗性是何等極差,
所以,當程式拋例外時,為了日志的可讀性,排查 Bug簡單,以及更好的用戶體驗性,所以我們要對全域例外進行處理,
二、開發環境
- JDK 1.8 或者1.8以上
- Springboot (此演示版本為 Springboot 2.1.18.RELEASE)
- Gradle (當然也可用Maven,其實目的都是為構建專案,管理依賴等)
三、添加依賴
plugins {
id "org.springframework.boot" version "2.1.18.RELEASE"
id "io.spring.dependency-management" version "1.0.10.RELEASE"
id "java"
}
group = 'com.nobody'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenLocal()
maven { url "http://maven.aliyun.com/nexus/content/groups/public/" }
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
// 添加lombok,主要為程式中通過注解,不用撰寫getter和setter等代碼
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
}
四、自定義例外錯誤類
在我們專案開發中,肯定會有跟業務相關的例外,例如添加用戶的業務,系統要求用戶名不能為空,但是添加用戶的請求介面,用戶名值為空,這時我們程式要報
用戶名不能為空的例外錯誤;或者查詢用戶資訊的介面,可能會報用戶不存在的錯誤例外等等,
4.1 自定義例外基礎介面類
因為要做成通用性,所以我們定義一個例外基礎介面類,自定義的例外列舉類需實作該介面,
package com.nobody.exception;
/**
* @Description 自定義例外基礎介面類,自定義的例外資訊列舉類需實作該介面,
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
public interface BaseErrorInfo {
/**
* 獲取錯誤碼
*
* @return 錯誤碼
*/
String getErrorCode();
/**
* 獲取錯誤資訊
*
* @return 錯誤資訊
*/
String getErrorMsg();
}
4.2 通用例外資訊列舉類
通用例外資訊列舉類,這里定義的所有例外資訊是整個程式通用的,
package com.nobody.exception;
import lombok.Getter;
/**
* @Description 自定義通用例外資訊列舉類
* @Author Mr.nobody
* @Date 2020/10/23
* @Version 1.0
*/
@Getter
public enum CommonErrorEnum implements BaseErrorInfo {
/**
* 成功
*/
SUCCESS("200", "成功!"),
/**
* 請求的資料格式不符!
*/
BODY_NOT_MATCH("400", "請求的資料格式不符!"),
/**
* 未找到該資源!
*/
NOT_FOUND("404", "未找到該資源!"),
/**
* 服務器內部錯誤!
*/
INTERNAL_SERVER_ERROR("500", "服務器內部錯誤!"),
/**
* 服務器正忙,請稍后再試!
*/
SERVER_BUSY("503", "服務器正忙,請稍后再試!");
private String errorCode;
private String errorMsg;
CommonErrorEnum(String errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
}
4.3 業務例外資訊列舉類
如果程式中例外資訊太多,可以針對每個模塊功能定義業務例外列舉類,方便維護,例如和用戶相關的例外資訊列舉類如下,
package com.nobody.exception;
import lombok.Getter;
/**
* @Description 自定義用戶相關例外資訊列舉類
* @Author Mr.nobody
* @Date 2020/10/23
* @Version 1.0
*/
@Getter
public enum UserErrorEnum implements BaseErrorInfo {
/**
* 用戶不存在
*/
USER_NOT_FOUND("1001", "用戶不存在!");
private String errorCode;
private String errorMsg;
UserErrorEnum(String errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
}
4.4 自定義業務例外類
業務例外類,主要用于業務錯誤,或者例外時手動拋出的例外,
package com.nobody.exception;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.MDC;
/**
* @Description 自定義業務例外類
* @Author Mr.nobody
* @Date 2020/10/23
* @Version 1.0
*/
@Getter
@Setter
public class BizException extends RuntimeException {
private static final long serialVersionUID = 5564446583860234738L;
// 錯誤碼
private String errorCode;
// 錯誤資訊
private String errorMsg;
// 日志追蹤ID
private String traceId = MDC.get("traceId");
public BizException(BaseErrorInfo errorInfo) {
super(errorInfo.getErrorMsg());
this.errorCode = errorInfo.getErrorCode();
this.errorMsg = errorInfo.getErrorMsg();
}
public BizException(BaseErrorInfo errorInfo, String errorMsg) {
super(errorMsg);
this.errorCode = errorInfo.getErrorCode();
this.errorMsg = errorMsg;
}
public BizException(BaseErrorInfo errorInfo, Throwable cause) {
super(errorInfo.getErrorMsg(), cause);
this.errorCode = errorInfo.getErrorCode();
this.errorMsg = errorInfo.getErrorMsg();
}
public BizException(String errorCode, String errorMsg) {
super(errorMsg);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public BizException(String errorCode, String errorMsg, Throwable cause) {
super(errorMsg, cause);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
}
五、介面回傳統一格式
為方便前端對介面回傳的資料進行處理,也是規范問題,所以我們要定義介面回傳統一格式,
package com.nobody.pojo.vo;
import lombok.Getter;
import lombok.Setter;
/**
* @Description 介面回傳統一格式
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
@Getter
@Setter
public class GeneralResult<T> {
private boolean success;
private String errorCode;
private String message;
private T data;
private String traceId;
private GeneralResult(boolean success, T data, String message, String errorCode) {
this.success = success;
this.data = data;
this.message = message;
this.errorCode = errorCode;
}
public static <T> GeneralResult<T> genResult(boolean success, T data, String message) {
return genResult(success, data, message, null);
}
public static <T> GeneralResult<T> genSuccessResult(T data) {
return genResult(true, data, null, null);
}
public static <T> GeneralResult<T> genErrorResult(String message) {
return genResult(false, null, message, null);
}
public static <T> GeneralResult<T> genSuccessResult() {
return genResult(true, null, null, null);
}
public static <T> GeneralResult<T> genErrorResult(String message, String errorCode) {
return genResult(false, null, message, errorCode);
}
public static <T> GeneralResult<T> genResult(boolean success, T data, String message,
String errorCode) {
return new GeneralResult<>(success, data, message, errorCode);
}
public static <T> GeneralResult<T> genErrorResult(String message, String errorCode,
String traceId) {
GeneralResult<T> result = genResult(false, null, message, errorCode);
result.setTraceId(traceId);
return result;
}
}
六、全域例外處理
此類是對全域例外的處理,根據自己情況,是否對不同種類的例外進行處理,例如以下是單獨對業務例外,介面引數例外,以及剩余的所有例外進行處理,并生成介面統一格式資訊,回傳給呼叫介面的客戶端,進行展示,
首先我們需要在處理全域例外的類上面,加上@ControllerAdvice或者@RestControllerAdvice注解,@ControllerAdvice 注解能處理@Controller和@RestController型別的介面呼叫時產生的例外,而 @RestControllerAdvice 注解只能處理@RestController型別介面呼叫時產生的例外,我們一般用 @ControllerAdvice 注解,
@ExceptionHandler只能注解在方法上,表示這是一個處理例外的方法,value屬性可以填寫需要處理的例外類,可以是陣列,
@ResponseBody注解表示我們回傳的資訊是回應體資料,
package com.nobody.exception;
import javax.servlet.http.HttpServletRequest;
import com.nobody.pojo.vo.GeneralResult;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @Description 統一例外處理
* @Author Mr.nobody
* @Date 2020/10/23
* @Version 1.0
*/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 處理自定義的業務例外
@ExceptionHandler(value = BizException.class)
@ResponseBody
public GeneralResult<Object> restErrorHandler(HttpServletRequest request, BizException e) {
String err = "requestURI:" + request.getRequestURI() + ",errorCode:" + e.getErrorCode()
+ ",errorMsg:" + e.getErrorMsg();
log.error(err, e);
return GeneralResult.genErrorResult(e.getMessage(), e.getErrorCode(), e.getTraceId());
}
// 處理介面引數資料格式錯誤例外
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ResponseBody
public GeneralResult<Object> errorHandler(HttpServletRequest request,
MethodArgumentNotValidException e) {
StringBuilder message = new StringBuilder();
String err = null;
e.getBindingResult().getAllErrors()
.forEach(error -> message.append(error.getDefaultMessage()).append(";"));
String des = message.toString();
if (!StringUtils.isEmpty(des)) {
err = des.substring(0, des.length() - 1);
}
log.error(err + ",requestURI:" + request.getRequestURI(), e);
return GeneralResult.genErrorResult(CommonErrorEnum.BODY_NOT_MATCH.getErrorMsg(),
CommonErrorEnum.BODY_NOT_MATCH.getErrorCode(), MDC.get("traceId"));
}
// 處理其他例外
@ExceptionHandler(value = Exception.class)
@ResponseBody
public GeneralResult<Object> errorHandler(HttpServletRequest request, Exception e) {
log.error("internal server error,requestURI:" + request.getRequestURI(), e);
return GeneralResult.genErrorResult(CommonErrorEnum.INTERNAL_SERVER_ERROR.getErrorMsg(),
CommonErrorEnum.INTERNAL_SERVER_ERROR.getErrorCode(), MDC.get("traceId"));
}
}
七、測驗
7.1 輔助類
測驗會針對不同情況進行驗證,以下是一些測驗需要用到的類,
package com.nobody.pojo.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* @Description 用戶物體類
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
@AllArgsConstructor
@Getter
@Setter
public class UserEntity implements Serializable {
private static final long serialVersionUID = 5564446583860234738L;
private String id;
private String name;
private int age;
}
package com.nobody.pojo.dto;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
/**
* @Description 添加用戶時引數類
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
@Getter
@Setter
public class UserDTO {
@NotEmpty(message = "用戶名不能為空")
private String name;
@Min(value = 0, message = "年齡最小不能低于0")
private int age;
}
以下簡單模擬 User 相關業務,然后產生不同的例外,
package com.nobody.service;
import com.nobody.pojo.dto.UserDTO;
import com.nobody.pojo.entity.UserEntity;
/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
public interface UserService {
UserEntity add(UserDTO userDTO);
UserEntity getById(String id);
void marry(String age);
}
package com.nobody.service.impl;
import com.nobody.exception.BizException;
import com.nobody.exception.UserErrorEnum;
import com.nobody.pojo.dto.UserDTO;
import com.nobody.pojo.entity.UserEntity;
import com.nobody.service.UserService;
import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.UUID;
/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
@Service
public class UserServiceImpl implements UserService {
@Override
public UserEntity add(UserDTO userDTO) {
String userId = UUID.randomUUID().toString();
return new UserEntity(userId, userDTO.getName(), userDTO.getAge());
}
@Override
public UserEntity getById(String id) {
// 模擬業務例外
if (Objects.equals(id, "000")) {
throw new BizException(UserErrorEnum.USER_NOT_FOUND);
}
return new UserEntity(id, "Mr.nobody", 18);
}
@Override
public void marry(String age) {
// 當age不是數字字串時,拋出例外
Integer integerAge = Integer.valueOf(age);
System.out.println(integerAge);
}
}
介面類定義,根據不同引數呼叫介面,可產生不同的例外錯誤,
package com.nobody.controller;
import com.nobody.pojo.dto.UserDTO;
import com.nobody.pojo.entity.UserEntity;
import com.nobody.pojo.vo.GeneralResult;
import com.nobody.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/6
* @Version 1.0
*/
@RestController
@RequestMapping("user")
public class UserController {
private UserService userService;
public UserController(final UserService userService) {
this.userService = userService;
}
@PostMapping("add")
public GeneralResult<UserEntity> add(@RequestBody @Valid UserDTO userDTO) {
UserEntity user = userService.add(userDTO);
return GeneralResult.genSuccessResult(user);
}
@GetMapping("find/{userId}")
public GeneralResult<UserEntity> find(@PathVariable String userId) {
UserEntity user = userService.getById(userId);
return GeneralResult.genSuccessResult(user);
}
@GetMapping("marry/{age}")
public GeneralResult<UserEntity> marry(@PathVariable String age) {
userService.marry(age);
return GeneralResult.genSuccessResult();
}
}
7.2 測驗結果
啟動服務,進行介面呼叫,本此演示用的 IDEA 自帶的
HTTP Client工具進行呼叫,當然你也可以使用Postman進行呼叫,

首先演示正常的介面呼叫,服務沒有報錯,介面也回傳正常資料,

還是呼叫查詢用戶介面,演示用戶不存在情況,服務報錯列印日志,介面也回傳錯誤資訊,


再演示添加用戶操作,用戶名不填值,程式報錯列印日志,介面也回傳錯誤資訊,



再演示其他例外情況,例如決議數字出錯,


此演示專案已上傳到Github,如有需要可自行下載,歡迎
Star,
https://github.com/LucioChn/springboot-global-exception-handler
關注微信公眾號【
Java之言】,更多干貨文章和學習資料,助你放棄編程之路!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/257782.html
標籤:java

