詳解SpringBoot中的統(tǒng)一結(jié)果返回與統(tǒng)一異常處理
引言
在開發(fā)Spring Boot應(yīng)用時,我們經(jīng)常面臨著不同的控制器方法需要處理各種不同類型的響應(yīng)結(jié)果,以及在代碼中分散處理異??赡軐?dǎo)致項目難以維護(hù)的問題。你是否曾經(jīng)遇到過在不同地方編寫相似的返回格式,或者在處理異常時感到有些混亂?這些看似小問題的積累,實際上可能對項目產(chǎn)生深遠(yuǎn)的影響。統(tǒng)一結(jié)果返回和統(tǒng)一異常處理并非只是為了規(guī)范代碼,更是為了提高團(tuán)隊的協(xié)作效率、降低項目維護(hù)的難度,并使代碼更易于理解和擴展。
本文的目的是幫助你更好地理解和應(yīng)用Spring Boot中的統(tǒng)一結(jié)果返回和統(tǒng)一異常處理。通過詳細(xì)的討論和實例演示,我們將為你提供一套清晰的指南,讓你能夠在自己的項目中輕松應(yīng)用這些技術(shù),提高代碼質(zhì)量,減輕開發(fā)壓力。
統(tǒng)一結(jié)果返回
統(tǒng)一結(jié)果返回是一種通過定義通用的返回格式,使所有的響應(yīng)結(jié)果都符合同一標(biāo)準(zhǔn)的方法。這有助于提高代碼的一致性,減少重復(fù)代碼的編寫,以及使客戶端更容易理解和處理API的響應(yīng)。統(tǒng)一結(jié)果返回不僅規(guī)范了代碼結(jié)構(gòu),還能提高團(tuán)隊協(xié)作效率,降低項目維護(hù)的難度。
接下來讓我們一起看看在SpringBoot中如何實現(xiàn)統(tǒng)一結(jié)果返回。
1. 定義通用的響應(yīng)對象
當(dāng)實現(xiàn)統(tǒng)一結(jié)果返回時,需要創(chuàng)建一個通用的響應(yīng)對象,定義成功和失敗的返回情況,并確保在接口中使用這個通用返回對象。
@Setter @Getter public class ResultResponse<T> implements Serializable { private static final long serialVersionUID = -1133637474601003587L; /** * 接口響應(yīng)狀態(tài)碼 */ private Integer code; /** * 接口響應(yīng)信息 */ private String msg; /** * 接口響應(yīng)的數(shù)據(jù) */ private T data; }
2. 定義接口響應(yīng)狀態(tài)碼
統(tǒng)一結(jié)果返回的關(guān)鍵之一是規(guī)定一套通用的狀態(tài)碼。這有助于客戶端更容易地理解和處理 API 的響應(yīng),同時也為開發(fā)者提供了一致的標(biāo)準(zhǔn)。通常,一些 HTTP 狀態(tài)碼已經(jīng)被廣泛接受,如:
200 OK
:表示成功處理請求。201 Created
:表示成功創(chuàng)建資源。204 No Content
:表示成功處理請求,但沒有返回任何內(nèi)容。
對于錯誤情況,也可以使用常見的 HTTP 狀態(tài)碼,如:
400 Bad Request
:客戶端請求錯誤。401 Unauthorized
:未授權(quán)訪問。404 Not Found
:請求資源不存在。500 Internal Server Error
:服務(wù)器內(nèi)部錯誤。
除了 HTTP 狀態(tài)碼外,你還可以定義自己的應(yīng)用程序特定狀態(tài)碼,以表示更具體的情況。確保文檔中清晰地說明了每個狀態(tài)碼所代表的含義,使開發(fā)者能夠正確地解釋和處理它們。
public enum StatusEnum { SUCCESS(200 ,"請求處理成功"), UNAUTHORIZED(401 ,"用戶認(rèn)證失敗"), FORBIDDEN(403 ,"權(quán)限不足"), SERVICE_ERROR(500, "服務(wù)器去旅行了,請稍后重試"), PARAM_INVALID(1000, "無效的參數(shù)"), ; public final Integer code; public final String message; StatusEnum(Integer code, String message) { this.code = code; this.message = message; } }
3. 定義統(tǒng)一的成功和失敗的處理方法
定義統(tǒng)一的成功和失敗的響應(yīng)方法有助于保持代碼一致性和規(guī)范性,簡化控制器邏輯,提高代碼復(fù)用性,降低維護(hù)成本,提高可讀性,促進(jìn)團(tuán)隊協(xié)作,以及更便于進(jìn)行測試。
/** * 封裝成功響應(yīng)的方法 * @param data 響應(yīng)數(shù)據(jù) * @return reponse * @param <T> 響應(yīng)數(shù)據(jù)類型 */ public static <T> ResultResponse<T> success(T data) { ResultResponse<T> response = new ResultResponse<>(); response.setData(data); response.setCode(StatusEnum.SUCCESS.code); return response; } /** * 封裝error的響應(yīng) * @param statusEnum error響應(yīng)的狀態(tài)值 * @return * @param <T> */ public static <T> ResultResponse<T> error(StatusEnum statusEnum) { return error(statusEnum, statusEnum.message); } /** * 封裝error的響應(yīng) 可自定義錯誤信息 * @param statusEnum error響應(yīng)的狀態(tài)值 * @return * @param <T> */ public static <T> ResultResponse<T> error(StatusEnum statusEnum, String errorMsg) { ResultResponse<T> response = new ResultResponse<>(); response.setCode(statusEnum.code); response.setMsg(errorMsg); return response; }
4. web層統(tǒng)一響應(yīng)結(jié)果
在web層使用統(tǒng)一結(jié)果返回的目的是將業(yè)務(wù)邏輯的處理結(jié)果按照預(yù)定的通用格式進(jìn)行封裝,以提高代碼的一致性和可讀性。
@RestController @RequestMapping("user") @Validated @Slf4j public class UserController { private IUserService userService; /** * 創(chuàng)建用戶 * @param requestVO * @return */ @PostMapping("create") public ResultResponse<Void> createUser(@Validated @RequestBody UserCreateRequestVO requestVO){ return ResultResponse.success(null); } /** * 根據(jù)用戶ID獲取用戶信息 * @param userId 用戶id * @return 用戶信息 */ @GetMapping("info") public ResultResponse<UserInfoResponseVO> getUser(@NotBlank(message = "請選擇用戶") String userId){ final UserInfoResponseVO responseVO = userService.getUserInfoById(userId); return ResultResponse.success(responseVO); } @Autowired public void setUserService(IUserService userService) { this.userService = userService; } }
調(diào)用接口,響應(yīng)的信息統(tǒng)一為:
{ "code": 200, "msg": null, "data": null } { "code": 200, "msg": null, "data": { "userId": "121", "userName": "碼農(nóng)Academy" } }
統(tǒng)一結(jié)果返回通過定義通用的返回格式、成功和失敗的返回情況,以及在控制器中使用這一模式,旨在提高代碼的一致性、可讀性和可維護(hù)性。采用統(tǒng)一的響應(yīng)格式簡化了業(yè)務(wù)邏輯處理流程,使得開發(fā)者更容易處理成功和失敗的情況,同時客戶端也更容易理解和處理 API 的響應(yīng)。這一實踐有助于降低維護(hù)成本、提高團(tuán)隊協(xié)作效率,并促進(jìn)代碼的規(guī)范化。
統(tǒng)一異常處理
統(tǒng)一異常處理的必要性體現(xiàn)在保持代碼的一致性、提供更清晰的錯誤信息、以及更容易排查問題。通過定義統(tǒng)一的異常處理方式,確保在整個應(yīng)用中對異常的處理保持一致,減少了重復(fù)編寫相似異常處理邏輯的工作,同時提供友好的錯誤信息幫助開發(fā)者和維護(hù)人員更快地定位和解決問題,最終提高了應(yīng)用的可維護(hù)性和可讀性。
1.定義統(tǒng)一的異常類
我們需要定義服務(wù)中可能拋出的自定義異常類。這些異常類可以繼承自RuntimeException
,并攜帶有關(guān)異常的相關(guān)信息。即可理解為局部異常,用于特定的業(yè)務(wù)處理中異常。手動埋點拋出。
@Getter public class ServiceException extends RuntimeException{ private static final long serialVersionUID = -3303518302920463234L; private final StatusEnum status; public ServiceException(StatusEnum status, String message) { super(message); this.status = status; } public ServiceException(StatusEnum status) { this(status, status.message); } }
2.異常處理器
創(chuàng)建一個全局的異常處理器,使用@ControllerAdvice
或者 @RestControllerAdvice
注解和@ExceptionHandler
注解來捕獲不同類型的異常,并定義處理邏輯。
2.1 @ControllerAdvice注解
用于聲明一個全局控制器建言(Advice),相當(dāng)于把@ExceptionHandler
、@InitBinder
和@ModelAttribute
注解的方法集中到一個地方。常放在一個特定的類上,這個類被認(rèn)為是全局異常處理器,可以跨足多個控制器。
當(dāng)時用@ControllerAdvice
時,我們需要在異常處理方法上加上@ResponseBody
,同理我們的web接口。但是如果我們使用@RestControllerAdvice
就可以不用加,同理也是web定義的接口
2.2 `@ExceptionHandler`注解
用于定義異常處理方法,處理特定類型的異常。放在全局異常處理器類中的具體方法上。
通過這兩個注解的配合,可以實現(xiàn)全局的異常處理。當(dāng)控制器中拋出異常時,Spring Boot會自動調(diào)用匹配的@ExceptionHandler
方法來處理異常,并返回定義的響應(yīng)。
@Slf4j @ControllerAdvice public class ExceptionAdvice { /** * 處理ServiceException * @param serviceException ServiceException * @param request 請求參數(shù) * @return 接口響應(yīng) */ @ExceptionHandler(ServiceException.class) @ResponseBody public ResultResponse<Void> handleServiceException(ServiceException serviceException, HttpServletRequest request) { log.warn("request {} throw ServiceException \n", request, serviceException); return ResultResponse.error(serviceException.getStatus(), serviceException.getMessage()); } /** * 其他異常攔截 * @param ex 異常 * @param request 請求參數(shù) * @return 接口響應(yīng) */ @ExceptionHandler(Exception.class) @ResponseBody public ResultResponse<Void> handleException(Exception ex, HttpServletRequest request) { log.error("request {} throw unExpectException \n", request, ex); return ResultResponse.error(StatusEnum.SERVICE_ERROR); } }
3.異常統(tǒng)一處理使用
在業(yè)務(wù)開發(fā)過程中,我們可以在service
層處理業(yè)務(wù)時,可以手動拋出業(yè)務(wù)異常。由全局異常處理器進(jìn)行統(tǒng)一處理。
@Service @Slf4j public class UserServiceImpl implements IUserService { private IUserManager userManager; /** * 創(chuàng)建用戶 * * @param requestVO 請求參數(shù) */ @Override public void createUser(UserCreateRequestVO requestVO) { final UserDO userDO = userManager.selectUserByName(requestVO.getUserName()); if (userDO != null){ throw new ServiceException(StatusEnum.PARAM_INVALID, "用戶名已存在"); } } @Autowired public void setUserManager(IUserManager userManager) { this.userManager = userManager; } } @RestController @RequestMapping("user") @Validated @Slf4j public class UserController { private IUserService userService; /** * 創(chuàng)建用戶 * @param requestVO * @return */ @PostMapping("create") public ResultResponse<Void> createUser(@Validated @RequestBody UserCreateRequestVO requestVO){ userService.createUser(requestVO); return ResultResponse.success(null); } @Autowired public void setUserService(IUserService userService) { this.userService = userService; } }
當(dāng)我們請求接口時,假如用戶名稱已存在,接口就會響應(yīng):
{ "code": 1000, "msg": "用戶名已存在", "data": null }
統(tǒng)一異常處理帶來的好處包括提供一致的異常響應(yīng)格式,簡化異常處理邏輯,記錄更好的錯誤日志,以及更容易排查和解決問題。通過統(tǒng)一處理異常,我們確保在整個應(yīng)用中對異常的處理方式一致,減少了重復(fù)性代碼的編寫,提高了代碼的規(guī)范性。簡化的異常處理邏輯降低了開發(fā)者的工作負(fù)擔(dān),而更好的錯誤日志有助于更迅速地定位和解決問題,最終提高了應(yīng)用的可維護(hù)性和穩(wěn)定性。
其他類型的異常處理
在項目開發(fā)過程中,我們還有一些常見的特定異常類型,比如MethodArgumentNotValidException
和UnexpectedTypeException
等,并為它們定義相應(yīng)的異常處理邏輯。這些特定異??赡苡捎谡埱髤?shù)校驗失敗或意外的數(shù)據(jù)類型問題而引起,因此有必要為它們單獨處理,以提供更具體和友好的異常響應(yīng)。
1.MethodArgumentNotValidException
由于請求參數(shù)校驗失敗引起的異常,通常涉及到使用@Valid
注解或者@Validated
進(jìn)行請求參數(shù)校驗。我們可以在異常處理器中編寫@ExceptionHandler
方法,捕獲并處理MethodArgumentNotValidException
,提取校驗錯誤信息,并返回詳細(xì)的錯誤響應(yīng)。
/** * 參數(shù)非法校驗 * @param ex * @return */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseBody public ResultResponse<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { try { List<ObjectError> errors = ex.getBindingResult().getAllErrors(); String message = errors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(",")); log.error("param illegal: {}", message); return ResultResponse.error(StatusEnum.PARAM_INVALID, message); } catch (Exception e) { return ResultResponse.error(StatusEnum.SERVICE_ERROR); } }
當(dāng)我們使用@Valid
注解或者@Validated
進(jìn)行請求參數(shù)校驗不通過時,響應(yīng)結(jié)果為:
{ "code": 1000, "msg": "請輸入地址信息,用戶年齡必須小于60歲,請輸入你的興趣愛好", "data": null }
關(guān)于@Valid
注解或者@Validated
進(jìn)行參數(shù)校驗的功能請參考:SpringBoot優(yōu)雅校驗參數(shù)
2.UnexpectedTypeException
意外的數(shù)據(jù)類型異常,通常表示程序運行時發(fā)生了不符合預(yù)期的數(shù)據(jù)類型問題。一個常見的使用場景是在數(shù)據(jù)轉(zhuǎn)換或類型處理的過程中。例如,在使用 Spring 表單綁定或數(shù)據(jù)綁定時,如果嘗試將一個不符合預(yù)期類型的值轉(zhuǎn)換為目標(biāo)類型,就可能拋出 UnexpectedTypeException
。這通常會發(fā)生在將字符串轉(zhuǎn)換為數(shù)字、日期等類型時,如果字符串的格式不符合目標(biāo)類型的要求。
我們可以在異常處理器中編寫@ExceptionHandler
方法,捕獲并處理UnexpectedTypeException
,提供適當(dāng)?shù)奶幚矸绞?,例如記錄錯誤日志,并返回合適的錯誤響應(yīng)。
@ExceptionHandler(UnexpectedTypeException.class) @ResponseBody public ResultResponse<Void> handleUnexpectedTypeException(UnexpectedTypeException ex, HttpServletRequest request) { log.error("catch UnexpectedTypeException, errorMessage: \n", ex); return ResultResponse.error(StatusEnum.PARAM_INVALID, ex.getMessage()); }
當(dāng)發(fā)生異常時,接口會響應(yīng):
{ "code": 500, "msg": "服務(wù)器去旅行了,請稍后重試", "data": null }
3.ConstraintViolationException
javax.validation.ConstraintViolationException
是 Java Bean Validation(JSR 380)中的一種異常。它通常在使用 Bean Validation 進(jìn)行數(shù)據(jù)校驗時,如果校驗失敗就會拋出這個異常。即我們在使用自定義校驗注解時,如果不滿足校驗規(guī)則,就會拋出這個錯誤。
@ExceptionHandler(ConstraintViolationException.class) @ResponseBody public ResultResponse<Void> handlerConstraintViolationException(ConstraintViolationException ex, HttpServletRequest request) { log.error("request {} throw ConstraintViolationException \n", request, ex); return ResultResponse.error(StatusEnum.PARAM_INVALID, ex.getMessage()); }
案例請參考:SpringBoot優(yōu)雅校驗參數(shù),注冊ConstraintValidator示例中的@UniqueUser
校驗。
4.HttpMessageNotReadableException
表示無法讀取HTTP消息的異常,通常由于請求體不合法或不可解析。
@ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(HttpMessageNotReadableException.class) public ResultResponse<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException ex, HttpServletRequest request) { log.error("request {} throw ucManagerException \n", request, ex); return ResultResponse.error(StatusEnum.SERVICE_ERROR); }
5.HttpRequestMethodNotSupportedException
Spring Framework 中的異常類,表示請求的 HTTP 方法不受支持。當(dāng)客戶端發(fā)送了一個使用不被服務(wù)器支持的 HTTP 方法(如 GET、POST、PUT、DELETE等)的請求時,可能會拋出這個異常。
@ExceptionHandler({HttpRequestMethodNotSupportedException.class, HttpMediaTypeException.class}) @ResponseBody public ResultResponse<Void> handleMethodNotSupportedException(Exception ex) { log.error("HttpRequestMethodNotSupportedException \n", ex); return ResultResponse.error(StatusEnum.HTTP_METHOD_NOT_SUPPORT); }
全局異常處理與局部異常處理在Spring Boot應(yīng)用開發(fā)中扮演不同角色。全局異常處理通過統(tǒng)一的異常處理器確保了整個應(yīng)用對異常的處理一致性,減少了冗余代碼,提高了代碼的整潔度。然而,這種方式可能在靈活性上略顯不足,無法滿足每個具體控制器或業(yè)務(wù)場景的個性化需求。
相比之下,局部異常處理能夠為每個控制器或業(yè)務(wù)場景提供更具體、靈活的異常處理邏輯,允許定制化的異常響應(yīng)。這使得在復(fù)雜的項目中更容易處理特定的異常情況,同時提供更詳細(xì)的錯誤信息。然而,局部異常處理可能帶來代碼冗余和維護(hù)難度的問題,特別是在大型項目中。
在實際應(yīng)用中,選擇全局異常處理還是局部異常處理應(yīng)根據(jù)項目規(guī)模和需求進(jìn)行權(quán)衡。對于小型項目或簡單場景,全局異常處理可能是一種更簡單、合適的選擇。而對于大型項目或需要個性化異常處理的復(fù)雜業(yè)務(wù)邏輯,局部異常處理則提供了更為靈活的方案。最佳實踐是在項目中根據(jù)具體情況靈活使用這兩種方式,以平衡一致性和個性化需求。
最佳實踐與注意事項
1. 最佳實踐
- 統(tǒng)一響應(yīng)格式: 在異常處理中,使用統(tǒng)一的響應(yīng)格式有助于客戶端更容易理解和處理錯誤。通常,返回一個包含錯誤碼、錯誤信息和可能的詳細(xì)信息的響應(yīng)對象。
- 詳細(xì)錯誤日志: 在異常處理中記錄詳細(xì)的錯誤日志,包括異常類型、發(fā)生時間、請求信息等。這有助于快速定位和解決問題。
- 使用HTTP狀態(tài)碼: 根據(jù)異常的性質(zhì),選擇適當(dāng)?shù)腍TTP狀態(tài)碼。例如,使用
HttpStatus.NOT_FOUND
表示資源未找到,HttpStatus.BAD_REQUEST
表示客戶端請求錯誤等。 - 異常分類: 根據(jù)異常的種類,合理分類處理??梢远x不同的異常類來表示不同的異常情況,然后在異常處理中使用
@ExceptionHandler
分別處理。 - 全局異常處理: 使用全局異常處理機制來捕獲未被特定控制器處理的異常,以確保應(yīng)用在整體上的健壯性。
2 注意事項
- 不濫用異常: 異常應(yīng)該用于表示真正的異常情況,而不是用作控制流程。濫用異??赡軐?dǎo)致性能問題和代碼可讀性降低。
- 不忽略異常: 避免在異常處理中忽略異常或僅僅打印日志而不進(jìn)行適當(dāng)?shù)奶幚?。這可能導(dǎo)致潛在的問題被掩蓋,難以追蹤和修復(fù)。
- 避免空的catch塊: 不要在
catch
塊中什么都不做,這樣會使得異常難以被發(fā)現(xiàn)。至少在catch
塊中記錄日志,以便了解異常的發(fā)生。 - 適時拋出異常: 不要過于吝嗇地拋出異常,但也不要無謂地濫用。在必要的時候使用異常,例如表示無法繼續(xù)執(zhí)行的錯誤情況。
- 測試異常場景: 編寫單元測試時,確保覆蓋異常場景,驗證異常的正確拋出和處理。
總結(jié)
異常處理在應(yīng)用開發(fā)中是至關(guān)重要的一環(huán),它能夠提高應(yīng)用的健壯性、可讀性和可維護(hù)性。全局異常處理和局部異常處理各有優(yōu)劣,需要根據(jù)項目的規(guī)模和需求來靈活選擇。通過采用統(tǒng)一的響應(yīng)格式、詳細(xì)的錯誤日志、適當(dāng)?shù)腍TTP狀態(tài)碼等最佳實踐,可以使異常處理更為有效和易于管理。同時,注意避免濫用異常、忽略異常、適時拋出異常等注意事項,有助于確保異常處理的質(zhì)量。在開發(fā)過程中,持續(xù)關(guān)注和優(yōu)化異常處理,將有助于提高應(yīng)用的穩(wěn)定性和用戶體驗。
以上就是詳解SpringBoot中的統(tǒng)一結(jié)果返回與統(tǒng)一異常處理的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot統(tǒng)一結(jié)果返回與統(tǒng)一異常處理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java多線程 Callable、Future 和FutureTask
這篇文章主要介紹Java多線程中的 Callable、Future 以及FutureTask,下面文章圍繞Java多線程的相關(guān)資料展開全文詳細(xì)內(nèi)容,需要的朋友可以參考一下2021-10-10Mybatis中注入執(zhí)行sql查詢、更新、新增及建表語句案例代碼
這篇文章主要介紹了Mybatis中注入執(zhí)行sql查詢、更新、新增以及建表語句,主要說明一個另類的操作,注入sql,并使用mybatis執(zhí)行,結(jié)合案例代碼詳解講解,需要的朋友可以參考下2023-02-02macOS中搭建Java8開發(fā)環(huán)境(基于Intel?x86?64-bit)
這篇文章主要介紹了macOS中搭建Java8開發(fā)環(huán)境(基于Intel?x86?64-bit)?的相關(guān)資料,需要的朋友可以參考下2022-12-12Spring Cloud Ribbon客戶端詳細(xì)介紹
Spring Cloud Ribbon 是一套基于 Netflix Ribbon 實現(xiàn)的客戶端負(fù)載均衡和服務(wù)調(diào)用工具。通過Spring Cloud的封裝,可以讓我們輕松地將面向服務(wù)的REST模版請求自動轉(zhuǎn)換成客戶端負(fù)載均衡的服務(wù)調(diào)用2022-09-09Spring?Boot開發(fā)RESTful接口與http協(xié)議狀態(tài)表述
這篇文章主要為大家介紹了Spring?Boot開發(fā)RESTful接口與http協(xié)議狀態(tài)表述,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03手把手帶你掌握SpringBoot RabbitMQ延遲隊列
RabbitMQ 是一個由Erlang語言開發(fā)的AMQP的開源實現(xiàn),支持多種客戶端。用于在分布式系統(tǒng)中存儲轉(zhuǎn)發(fā)消息,在易用性、擴展性、高可用性等方面表現(xiàn)不俗,下文將帶你深入了解 RabbitMQ 延遲隊列2021-09-09