基于Spring實(shí)現(xiàn)自定義錯(cuò)誤信息返回詳解
背景
Spring 提供了 @RestControllerAdvice 用來(lái)實(shí)現(xiàn) HTTP 協(xié)議的全局異常處理。在異常信息的處理上通常只返回特定的 Response 對(duì)象,如下。
@Slf4j
@RestControllerAdvice
public class RestExceptionResolver {
@ExceptionHandler(Exception.class)
public ResponseEntity<?> processException(Exception ex) {
BodyBuilder builder;
Response response;
ResponseStatus responseStatus =
AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
if (responseStatus != null) {
builder = ResponseEntity.status(responseStatus.value());
response = Response.buildFailure("500", responseStatus.reason());
} else {
builder = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
response = Response.buildFailure("500", ex.getMessage());
}
this.process(ex, response);
return builder.body(response);
}
}
作為基礎(chǔ)框架,筆者就遇到項(xiàng)目A 要求返回 Response1 對(duì)象,項(xiàng)目B 要求返回 Response2 對(duì)象,這個(gè)時(shí)候,適配起來(lái)就很痛苦,例如下方的代碼。
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ToString(callSuper = true)
@Data
public class Response extends DTO {
private static final long serialVersionUID = 1L;
private boolean success;
private String errCode; // 項(xiàng)目A 要求錯(cuò)誤碼是字符型
private String errMessage; // 項(xiàng)目A 要求用這個(gè)名字
private int code; // 項(xiàng)目B 要求錯(cuò)誤碼是整型
private String message; // 項(xiàng)目B 要求用這個(gè)名字
}
另外,@RestControllerAdvice 只適用于 Web 異常捕獲,我們還要考慮其他組件的情況,例如 Dubbo 捕獲 RPC 異常、Sentinel 組件觸發(fā)限流、Spring Security 安全框架拋出認(rèn)證異常。
目標(biāo)
不需要修改基礎(chǔ)框架,允許業(yè)務(wù)方自行擴(kuò)展異常返回對(duì)象。
實(shí)現(xiàn)
將 Response 提煉為 Builder 模式,改為 ResponseBuilder.builder() 構(gòu)建返回對(duì)象。
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<?> resolveException(Exception ex) {
BodyBuilder builder;
Object response;
ResponseStatus status = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
if (status != null) {
builder = ResponseEntity.status(status.value());
response = ResponseBuilder.builder().buildFailure("500", status.reason());
} else {
builder = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
response = ResponseBuilder.builder().buildFailure("500", ex.getMessage());
}
this.postProcess(ex);
return builder.body(response);
}
}
public interface ResponseBuilder<T> {
static ResponseBuilder<?> builder() {
// 嘗試從業(yè)務(wù)項(xiàng)目獲取自定義的 Spring Bean
ResponseBuilder<?> builder = ApplicationContextHelper.getBean(ResponseBuilder.class);
if (builder != null) {
return builder;
}
// 如果業(yè)務(wù)項(xiàng)目沒(méi)有自定義 Bean,返回默認(rèn)的 Builder
return DefalutResponseBuilder.getInstance();
}
T buildSuccess();
<Body> T buildSuccess(Body data);
T buildFailure(String errCode, String errMessage, Object... params);
}
public class DefalutResponseBuilder implements ResponseBuilder<Response> {
private static final DefaultResponseBuilder INSTANCE = new DefaultResponseBuilder();
private DefaultResponseBuilder() {}
public static DefaultResponseBuilder getInstance() {
return INSTANCE;
}
@Override
public Response buildSuccess() {
Response response = new Response();
response.setSuccess(true);
return response;
}
@Override
public <Body> Response buildSuccess(Body data) {
SingleResponse<Body> response = new SingleResponse<>();
response.setSuccess(true);
response.setData(data);
return response;
}
@Override
public Response buildFailure(String errCode, String errMessage, Object... params) {
Response response = new Response();
response.setSuccess(false);
response.setErrCode(errCode);
response.setErrMessage(MessageFormatter.arrayFormat(message, placeholders).getMessage());
return response;
}
}
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ToString(callSuper = true)
@Data
public class Response extends DTO {
private static final long serialVersionUID = 1L;
private boolean success;
private String errCode;
private String errMessage;
}
業(yè)務(wù)方覺(jué)得 Response 不能滿足需求,重新定義了新對(duì)象,如下。
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ToString(callSuper = true)
@Data
public class CustomResponse {
private static final long serialVersionUID = 1L;
private boolean success;
private int code; // 要求錯(cuò)誤碼是整型
private String message; // 前端要求用這個(gè)名字
}創(chuàng)建 CustomResponseBuilder 包裝 CustomResponse 對(duì)象,并標(biāo)記 @Component 注解,放入 Spring Bean 管理。
@Component
public class CustomResponseBuilder implements ResponseBuilder<CustomResponse> {
@Override
public CustomResponse buildSuccess() {
CustomResponse response = new CustomResponse();
response.setSuccess(true);
return response;
}
@Override
public <Body> CustomResponse buildSuccess(Body data) {
// 略
}
@Override
public CustomResponse buildFailure(int code, String message, Object... params) {
CustomResponse response = new CustomResponse();
response.setSuccess(false);
response.setCode(code);
response.setMessage(MessageFormatter.arrayFormat(message, placeholders).getMessage());
return response;
}
}
上述已提到 ResponseBuilder.builder() 優(yōu)先查找 Spring Bean,所以 CustomResponseBuilder 覆蓋了框架內(nèi)置的 DefaultResponseBuilder 類,全局異常捕獲器返回結(jié)果時(shí),就能返回業(yè)務(wù)方自定義的 CustomResponse 對(duì)象,這樣,不需要改動(dòng)框架,就能滿足業(yè)務(wù)需求。
產(chǎn)出
根據(jù)這個(gè)思路,我們分別實(shí)現(xiàn)了 Web 異常、Dubbo 異常、Sentinel 限流、Security 認(rèn)證等各種場(chǎng)景的異常處理機(jī)制,業(yè)務(wù)方只需要自行創(chuàng)建 ResponseBuilder 擴(kuò)展自己的返回對(duì)象即可,不需要修改框架。
到此這篇關(guān)于基于Spring實(shí)現(xiàn)自定義錯(cuò)誤信息返回詳解的文章就介紹到這了,更多相關(guān)Spring自定義錯(cuò)誤信息內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決Java變異出現(xiàn)錯(cuò)誤No enclosing instance of type XXX is accessible
這牌你文章主要給大家分享解決Java變異出現(xiàn)錯(cuò)誤,具體的饑餓絕方案請(qǐng)看下面文章的內(nèi)容,需要的朋友可以參考一下,希望能幫助到你2021-09-09
java實(shí)現(xiàn)把兩個(gè)有序數(shù)組合并到一個(gè)數(shù)組的實(shí)例
今天小編就為大家分享一篇java實(shí)現(xiàn)把兩個(gè)有序數(shù)組合并到一個(gè)數(shù)組的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Java語(yǔ)法中Lambda表達(dá)式無(wú)法拋出異常的解決
這篇文章主要介紹了Java語(yǔ)法中Lambda表達(dá)式無(wú)法拋出異常的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
java實(shí)現(xiàn)數(shù)字轉(zhuǎn)大寫的方法
這篇文章主要介紹了 java實(shí)現(xiàn)數(shù)字轉(zhuǎn)大寫的方法的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-10-10
創(chuàng)建springBoot模塊沒(méi)有目錄結(jié)構(gòu)的解決方案
2023版IntelliJ IDEA創(chuàng)建模塊時(shí)可能出現(xiàn)目錄結(jié)構(gòu)識(shí)別錯(cuò)誤,導(dǎo)致文件顯示異常,解決方法為選擇模塊后點(diǎn)擊確認(rèn),重新校準(zhǔn)項(xiàng)目結(jié)構(gòu)設(shè)置,確保源代碼和資源目錄正確配置2025-08-08
java數(shù)據(jù)結(jié)構(gòu)與算法之簡(jiǎn)單選擇排序詳解
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)與算法之簡(jiǎn)單選擇排序,結(jié)合實(shí)例形式分析了選擇排序的原理、實(shí)現(xiàn)方法與相關(guān)操作技巧,需要的朋友可以參考下2017-05-05
java網(wǎng)上圖書商城(5)購(gòu)物車模塊2
這篇文章主要為大家詳細(xì)介紹了java網(wǎng)上圖書商城,購(gòu)物車模塊第二篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
解決java idea新建子目錄時(shí)命名不是樹(shù)形結(jié)構(gòu)的問(wèn)題
這篇文章主要介紹了解決java idea新建子目錄時(shí)命名不是樹(shù)形結(jié)構(gòu)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Spring探秘之如何妙用BeanPostProcessor
BeanPostProcessor也稱為Bean后置處理器,它是Spring中定義的接口,在Spring容器的創(chuàng)建過(guò)程中會(huì)回調(diào)BeanPostProcessor中定義的兩個(gè)方法,這篇文章主要給大家介紹了關(guān)于Spring探秘之如何妙用BeanPostProcessor的相關(guān)資料,需要的朋友可以參考下2022-01-01

