spring boot教程之全局處理異常封裝
1|1簡(jiǎn)介
在項(xiàng)目中經(jīng)常出現(xiàn)系統(tǒng)異常的情況,比如NullPointerException等等。如果默認(rèn)未處理的情況下,springboot會(huì)響應(yīng)默認(rèn)的錯(cuò)誤提示,這樣對(duì)用戶體驗(yàn)不是友好,系統(tǒng)層面的錯(cuò)誤,用戶不能感知到,即使為500的錯(cuò)誤,可以給用戶提示一個(gè)類似服務(wù)器開(kāi)小差的友好提示等。
在微服務(wù)里,每個(gè)服務(wù)中都會(huì)有異常情況,幾乎所有服務(wù)的默認(rèn)異常處理配置一致,導(dǎo)致很多重復(fù)編碼,我們將這些重復(fù)默認(rèn)異常處理可以抽出一個(gè)公共starter包,各個(gè)服務(wù)依賴即可,定制化異常處理在各個(gè)模塊里開(kāi)發(fā)。
1|2配置
unified-dispose-springboot-starter
這個(gè)模塊里包含異常處理以及全局返回封裝等功能,下面。
完整目錄結(jié)構(gòu)如下:
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── purgetiem
│ │ │ └── starter
│ │ │ └── dispose
│ │ │ ├── GlobalDefaultConfiguration.java
│ │ │ ├── GlobalDefaultProperties.java
│ │ │ ├── Interceptors.java
│ │ │ ├── Result.java
│ │ │ ├── advice
│ │ │ │ └── CommonResponseDataAdvice.java
│ │ │ ├── annotation
│ │ │ │ ├── EnableGlobalDispose.java
│ │ │ │ └── IgnorReponseAdvice.java
│ │ │ └── exception
│ │ │ ├── GlobalDefaultExceptionHandler.java
│ │ │ ├── category
│ │ │ │ └── BusinessException.java
│ │ │ └── error
│ │ │ ├── CommonErrorCode.java
│ │ │ └── details
│ │ │ └── BusinessErrorCode.java
│ │ └── resources
│ │ ├── META-INF
│ │ │ └── spring.factories
│ │ └── dispose.properties
│ └── test
│ └── java
異常處理
@RestControllerAdvice 或者 @ControllerAdvice為spring的異常處理注解。
我們先創(chuàng)建GlobalDefaultExceptionHandler 全局異常處理類:
@RestControllerAdvice
public class GlobalDefaultExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalDefaultExceptionHandler.class);
/**
* NoHandlerFoundException 404 異常處理
*/
@ExceptionHandler(value = NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Result handlerNoHandlerFoundException(NoHandlerFoundException exception) {
outPutErrorWarn(NoHandlerFoundException.class, CommonErrorCode.NOT_FOUND, exception);
return Result.ofFail(CommonErrorCode.NOT_FOUND);
}
/**
* HttpRequestMethodNotSupportedException 405 異常處理
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result handlerHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException exception) {
outPutErrorWarn(HttpRequestMethodNotSupportedException.class,
CommonErrorCode.METHOD_NOT_ALLOWED, exception);
return Result.ofFail(CommonErrorCode.METHOD_NOT_ALLOWED);
}
/**
* HttpMediaTypeNotSupportedException 415 異常處理
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public Result handlerHttpMediaTypeNotSupportedException(
HttpMediaTypeNotSupportedException exception) {
outPutErrorWarn(HttpMediaTypeNotSupportedException.class,
CommonErrorCode.UNSUPPORTED_MEDIA_TYPE, exception);
return Result.ofFail(CommonErrorCode.UNSUPPORTED_MEDIA_TYPE);
}
/**
* Exception 類捕獲 500 異常處理
*/
@ExceptionHandler(value = Exception.class)
public Result handlerException(Exception e) {
return ifDepthExceptionType(e);
}
/**
* 二次深度檢查錯(cuò)誤類型
*/
private Result ifDepthExceptionType(Throwable throwable) {
Throwable cause = throwable.getCause();
if (cause instanceof ClientException) {
return handlerClientException((ClientException) cause);
}
if (cause instanceof FeignException) {
return handlerFeignException((FeignException) cause);
}
outPutError(Exception.class, CommonErrorCode.EXCEPTION, throwable);
return Result.ofFail(CommonErrorCode.EXCEPTION);
}
/**
* FeignException 類捕獲
*/
@ExceptionHandler(value = FeignException.class)
public Result handlerFeignException(FeignException e) {
outPutError(FeignException.class, CommonErrorCode.RPC_ERROR, e);
return Result.ofFail(CommonErrorCode.RPC_ERROR);
}
/**
* ClientException 類捕獲
*/
@ExceptionHandler(value = ClientException.class)
public Result handlerClientException(ClientException e) {
outPutError(ClientException.class, CommonErrorCode.RPC_ERROR, e);
return Result.ofFail(CommonErrorCode.RPC_ERROR);
}
/**
* BusinessException 類捕獲
*/
@ExceptionHandler(value = BusinessException.class)
public Result handlerBusinessException(BusinessException e) {
outPutError(BusinessException.class, CommonErrorCode.BUSINESS_ERROR, e);
return Result.ofFail(e.getCode(), e.getMessage());
}
/**
* HttpMessageNotReadableException 參數(shù)錯(cuò)誤異常
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
outPutError(HttpMessageNotReadableException.class, CommonErrorCode.PARAM_ERROR, e);
String msg = String.format("%s : 錯(cuò)誤詳情( %s )", CommonErrorCode.PARAM_ERROR.getMessage(),
e.getRootCause().getMessage());
return Result.ofFail(CommonErrorCode.PARAM_ERROR.getCode(), msg);
}
/**
* BindException 參數(shù)錯(cuò)誤異常
*/
@ExceptionHandler(BindException.class)
public Result handleMethodArgumentNotValidException(BindException e) {
outPutError(BindException.class, CommonErrorCode.PARAM_ERROR, e);
BindingResult bindingResult = e.getBindingResult();
return getBindResultDTO(bindingResult);
}
private Result getBindResultDTO(BindingResult bindingResult) {
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
if (log.isDebugEnabled()) {
for (FieldError error : fieldErrors) {
log.error("{} -> {}", error.getDefaultMessage(), error.getDefaultMessage());
}
}
if (fieldErrors.isEmpty()) {
log.error("validExceptionHandler error fieldErrors is empty");
Result.ofFail(CommonErrorCode.BUSINESS_ERROR.getCode(), "");
}
return Result
.ofFail(CommonErrorCode.PARAM_ERROR.getCode(), fieldErrors.get(0).getDefaultMessage());
}
public void outPutError(Class errorType, Enum secondaryErrorType, Throwable throwable) {
log.error("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage(),
throwable);
}
public void outPutErrorWarn(Class errorType, Enum secondaryErrorType, Throwable throwable) {
log.warn("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage());
}
}
大致內(nèi)容處理了一些項(xiàng)目常見(jiàn)的異常Exception,BindException參數(shù)異常等。
這里將默認(rèn)的404、405、415等默認(rèn)http狀態(tài)碼也重寫了。
重寫這個(gè)默認(rèn)的狀態(tài)碼需要配置throw-exception-if-no-handler-found以及add-mappings。
# 出現(xiàn)錯(cuò)誤時(shí), 直接拋出異常(便于異常統(tǒng)一處理,否則捕獲不到404) spring.mvc.throw-exception-if-no-handler-found=true # 是否開(kāi)啟默認(rèn)的資源處理,默認(rèn)為true spring.resources.add-mappings=false
ps: 請(qǐng)注意這兩個(gè)配置會(huì)將靜態(tài)資源忽略。
請(qǐng)產(chǎn)考WebMvcAutoConfiguration#addResourceHandlers
Exception為了防止未知的異常沒(méi)有防護(hù)到,默認(rèn)給用戶返回服務(wù)器開(kāi)小差,請(qǐng)稍后再試等提示。
具體異常默認(rèn)會(huì)以小到大去匹配。
如果拋出BindException,自定義有BindException就會(huì)去這個(gè)處理器里處理。沒(méi)有就會(huì)走到它的父類去匹配,請(qǐng)參考java-異常體系。

其他已知異??梢宰约河聾ExceptionHandler注解進(jìn)行捕獲處理。
通用異常枚舉
為了避免異常值不好維護(hù),我們使用CommonErrorCode枚舉把常見(jiàn)的異常提示維護(hù)起來(lái)。
@Getter
public enum CommonErrorCode {
/**
* 404 Web 服務(wù)器找不到您所請(qǐng)求的文件或腳本。請(qǐng)檢查URL 以確保路徑正確。
*/
NOT_FOUND("CLOUD-404",
String.format("哎呀,無(wú)法找到這個(gè)資源啦(%s)", HttpStatus.NOT_FOUND.getReasonPhrase())),
/**
* 405 對(duì)于請(qǐng)求所標(biāo)識(shí)的資源,不允許使用請(qǐng)求行中所指定的方法。請(qǐng)確保為所請(qǐng)求的資源設(shè)置了正確的 MIME 類型。
*/
METHOD_NOT_ALLOWED("CLOUD-405",
String.format("請(qǐng)換個(gè)姿勢(shì)操作試試(%s)", HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase())),
/**
* 415 Unsupported Media Type
*/
UNSUPPORTED_MEDIA_TYPE("CLOUD-415",
String.format("呀,不支持該媒體類型(%s)", HttpStatus.UNSUPPORTED_MEDIA_TYPE.getReasonPhrase())),
/**
* 系統(tǒng)異常 500 服務(wù)器的內(nèi)部錯(cuò)誤
*/
EXCEPTION("CLOUD-500", "服務(wù)器開(kāi)小差,請(qǐng)稍后再試"),
/**
* 系統(tǒng)限流
*/
TRAFFIC_LIMITING("CLOUD-429", "哎呀,網(wǎng)絡(luò)擁擠請(qǐng)稍后再試試"),
/**
* 服務(wù)調(diào)用異常
*/
API_GATEWAY_ERROR("API-9999", "網(wǎng)絡(luò)繁忙,請(qǐng)稍后再試"),
/**
* 參數(shù)錯(cuò)誤
*/
PARAM_ERROR("CLOUD-100", "參數(shù)錯(cuò)誤"),
/**
* 業(yè)務(wù)異常
*/
BUSINESS_ERROR("CLOUD-400", "業(yè)務(wù)異常"),
/**
* rpc調(diào)用異常
*/
RPC_ERROR("RPC-510", "呀,網(wǎng)絡(luò)出問(wèn)題啦!");
private String code;
private String message;
CommonErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
}
其實(shí)starter包中不建議使用@Getter等lombok注解,防止他人未使用lombok依賴該項(xiàng)目出現(xiàn)問(wèn)題。
通用業(yè)務(wù)異常
這兩個(gè)類完成基本可以正常使用異常攔截了,不過(guò)為了業(yè)務(wù)方便,我們創(chuàng)建一個(gè)一般通用的業(yè)務(wù)異常。
BusinessException繼承RuntimeException即可。
@Getter
public class BusinessException extends RuntimeException {
private String code;
private boolean isShowMsg = true;
/**
* 使用枚舉傳參
*
* @param errorCode 異常枚舉
*/
public BusinessException(BusinessErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
/**
* 使用自定義消息
*
* @param code 值
* @param msg 詳情
*/
public BusinessException(String code, String msg) {
super(msg);
this.code = code;
}
}
將BusinessException加入GlobalDefaultExceptionHandler全局異常攔截。
/**
* BusinessException 類捕獲
*/
@ExceptionHandler(value = BusinessException.class)
public Result handlerBusinessException(BusinessException e) {
outPutError(BusinessException.class, CommonErrorCode.BUSINESS_ERROR, e);
return Result.ofFail(e.getCode(), e.getMessage());
}
程序主動(dòng)拋出異常可以通過(guò)下面方式:
throw new BusinessException(BusinessErrorCode.BUSINESS_ERROR);
// 或者
throw new BusinessException("CLOUD800","沒(méi)有多余的庫(kù)存");
通常不建議直接拋出通用的BusinessException異常,應(yīng)當(dāng)在對(duì)應(yīng)的模塊里添加對(duì)應(yīng)的領(lǐng)域的異常處理類以及對(duì)應(yīng)的枚舉錯(cuò)誤類型。
如會(huì)員模塊:
創(chuàng)建UserException異常類、UserErrorCode枚舉、以及UserExceptionHandler統(tǒng)一攔截類。
UserException:
@Data
public class UserException extends RuntimeException {
private String code;
private boolean isShowMsg = true;
/**
* 使用枚舉傳參
*
* @param errorCode 異常枚舉
*/
public UserException(UserErrorCode errorCode) {
super(errorCode.getMessage());
this.setCode(errorCode.getCode());
}
}
UserErrorCode:
@Getter
public enum UserErrorCode {
/**
* 權(quán)限異常
*/
NOT_PERMISSIONS("CLOUD401","您沒(méi)有操作權(quán)限"),
;
private String code;
private String message;
CommonErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
}
UserExceptionHandler:
@Slf4j
@RestControllerAdvice
public class UserExceptionHandler {
/**
* UserException 類捕獲
*/
@ExceptionHandler(value = UserException.class)
public Result handler(UserException e) {
log.error(e.getMessage(), e);
return Result.ofFail(e.getCode(), e.getMessage());
}
}
最后業(yè)務(wù)使用如下:
// 判斷是否有權(quán)限拋出異常 throw new UserException(UserErrorCode.NOT_PERMISSIONS);
加入spring容器
最后將GlobalDefaultExceptionHandler以bean的方式注入spring容器。
@Configuration
@EnableConfigurationProperties(GlobalDefaultProperties.class)
@PropertySource(value = "classpath:dispose.properties", encoding = "UTF-8")
public class GlobalDefaultConfiguration {
@Bean
public GlobalDefaultExceptionHandler globalDefaultExceptionHandler() {
return new GlobalDefaultExceptionHandler();
}
@Bean
public CommonResponseDataAdvice commonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties){
return new CommonResponseDataAdvice(globalDefaultProperties);
}
}
將GlobalDefaultConfiguration在resources/META-INF/spring.factories文件下加載。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.purgetime.starter.dispose.GlobalDefaultConfiguration
不過(guò)我們這次使用注解方式開(kāi)啟。其他項(xiàng)目依賴包后,需要添加@EnableGlobalDispose才可以將全局?jǐn)r截的特性開(kāi)啟。
將剛剛創(chuàng)建的spring.factories注釋掉,創(chuàng)建EnableGlobalDispose注解。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(GlobalDefaultConfiguration.class)
public @interface EnableGlobalDispose {
}
使用@Import將GlobalDefaultConfiguration導(dǎo)入即可。
使用
添加依賴
<dependency> <groupId>io.deepblueai</groupId> <artifactId>unified-dispose-deepblueai-starter</artifactId> <version>0.1.0.RELEASE</version> </dependency>
啟動(dòng)類開(kāi)啟@EnableGlobalDispose注解即可。
1|3總結(jié)
項(xiàng)目里很多重復(fù)的code,我們可以通過(guò)一定的方式去簡(jiǎn)化,以達(dá)到一定目的減少開(kāi)發(fā)量。
示例代碼地址:unified-dispose-springboot
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
JDBC連接SQL?Server數(shù)據(jù)庫(kù)實(shí)現(xiàn)增刪改查的全過(guò)程
實(shí)際開(kāi)發(fā)中手動(dòng)的輸入SQL語(yǔ)句是少之又少,大多數(shù)情況下是通過(guò)編譯代碼進(jìn)行來(lái)控制自動(dòng)執(zhí)行,下面這篇文章主要給大家介紹了關(guān)于JDBC連接SQL?Server數(shù)據(jù)庫(kù)實(shí)現(xiàn)增刪改查的相關(guān)資料,需要的朋友可以參考下2023-04-04
SpringBoot讀取配置優(yōu)先級(jí)順序的方法詳解
Spring Boot作為一種輕量級(jí)的Java應(yīng)用程序框架,以其開(kāi)箱即用、快速搭建新項(xiàng)目的特性贏得了廣大開(kāi)發(fā)者的青睞,在Spring Boot生態(tài)系統(tǒng)中,配置屬性可以從各種來(lái)源獲取,本文將深入探討Spring Boot加載外部配置屬性的優(yōu)先級(jí)規(guī)則,需要的朋友可以參考下2024-05-05
JAVA中while循環(huán)的使用與注意事項(xiàng)
這篇文章主要介紹了while循環(huán)在編程中的應(yīng)用,包括其基本結(jié)構(gòu)、語(yǔ)句示例、適用場(chǎng)景以及注意事項(xiàng),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-01-01
Spring Cloud項(xiàng)目前后端分離跨域的操作
這篇文章主要介紹了Spring Cloud項(xiàng)目前后端分離跨域的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
Java封裝公共Result結(jié)果返回類的實(shí)現(xiàn)
在使用Java開(kāi)發(fā)接口請(qǐng)求中,我們需要對(duì)請(qǐng)求進(jìn)行進(jìn)行統(tǒng)一返回值,這時(shí)候我們自己封裝一個(gè)統(tǒng)一的Result返回類,本文主要介紹了Java封裝公共Result結(jié)果返回類的實(shí)現(xiàn),感興趣的可以了解一下2023-01-01
使用maven工具解決jar包沖突或重復(fù)加載的問(wèn)題
這篇文章主要介紹了使用maven工具解決jar包沖突或重復(fù)加載的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
使用WebSocket實(shí)現(xiàn)即時(shí)通訊(一個(gè)群聊的聊天室)
這篇文章主要為大家詳細(xì)介紹了使用WebSocket實(shí)現(xiàn)即使通訊,實(shí)現(xiàn)一個(gè)群聊的聊天室,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
IDEA如何使用spring-Initializr快速搭建SpringBoot
這篇文章主要介紹了IDEA如何使用spring-Initializr快速搭建SpringBoot問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
java開(kāi)發(fā)中的誤區(qū)和細(xì)節(jié)整理
這篇文章給大家整理了關(guān)于JAVA開(kāi)發(fā)中的細(xì)節(jié)以及經(jīng)常進(jìn)入的誤區(qū)整理,希望我們整理的內(nèi)容能夠給大家提供到幫助。2018-04-04

