詳解SpringBoot中的統(tǒng)一結(jié)果返回與統(tǒng)一異常處理
引言
在開發(fā)Spring Boot應(yīng)用時,我們經(jīng)常面臨著不同的控制器方法需要處理各種不同類型的響應(yīng)結(jié)果,以及在代碼中分散處理異??赡軐?dǎo)致項目難以維護的問題。你是否曾經(jīng)遇到過在不同地方編寫相似的返回格式,或者在處理異常時感到有些混亂?這些看似小問題的積累,實際上可能對項目產(chǎn)生深遠的影響。統(tǒng)一結(jié)果返回和統(tǒng)一異常處理并非只是為了規(guī)范代碼,更是為了提高團隊的協(xié)作效率、降低項目維護的難度,并使代碼更易于理解和擴展。
本文的目的是幫助你更好地理解和應(yīng)用Spring Boot中的統(tǒng)一結(jié)果返回和統(tǒng)一異常處理。通過詳細的討論和實例演示,我們將為你提供一套清晰的指南,讓你能夠在自己的項目中輕松應(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),還能提高團隊協(xié)作效率,降低項目維護的難度。
接下來讓我們一起看看在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ù)用性,降低維護成本,提高可讀性,促進團隊協(xié)作,以及更便于進行測試。
/**
* 封裝成功響應(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ù)定的通用格式進行封裝,以提高代碼的一致性和可讀性。
@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é)果返回通過定義通用的返回格式、成功和失敗的返回情況,以及在控制器中使用這一模式,旨在提高代碼的一致性、可讀性和可維護性。采用統(tǒng)一的響應(yīng)格式簡化了業(yè)務(wù)邏輯處理流程,使得開發(fā)者更容易處理成功和失敗的情況,同時客戶端也更容易理解和處理 API 的響應(yīng)。這一實踐有助于降低維護成本、提高團隊協(xié)作效率,并促進代碼的規(guī)范化。
統(tǒng)一異常處理
統(tǒng)一異常處理的必要性體現(xiàn)在保持代碼的一致性、提供更清晰的錯誤信息、以及更容易排查問題。通過定義統(tǒng)一的異常處理方式,確保在整個應(yīng)用中對異常的處理保持一致,減少了重復(fù)編寫相似異常處理邏輯的工作,同時提供友好的錯誤信息幫助開發(fā)者和維護人員更快地定位和解決問題,最終提高了應(yīng)用的可維護性和可讀性。
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ù)異常。由全局異常處理器進行統(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ā)者的工作負擔(dān),而更好的錯誤日志有助于更迅速地定位和解決問題,最終提高了應(yīng)用的可維護性和穩(wěn)定性。
其他類型的異常處理
在項目開發(fā)過程中,我們還有一些常見的特定異常類型,比如MethodArgumentNotValidException和UnexpectedTypeException等,并為它們定義相應(yīng)的異常處理邏輯。這些特定異??赡苡捎谡埱髤?shù)校驗失敗或意外的數(shù)據(jù)類型問題而引起,因此有必要為它們單獨處理,以提供更具體和友好的異常響應(yīng)。
1.MethodArgumentNotValidException
由于請求參數(shù)校驗失敗引起的異常,通常涉及到使用@Valid注解或者@Validated進行請求參數(shù)校驗。我們可以在異常處理器中編寫@ExceptionHandler方法,捕獲并處理MethodArgumentNotValidException,提取校驗錯誤信息,并返回詳細的錯誤響應(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進行請求參數(shù)校驗不通過時,響應(yīng)結(jié)果為:
{
"code": 1000,
"msg": "請輸入地址信息,用戶年齡必須小于60歲,請輸入你的興趣愛好",
"data": null
}
關(guān)于@Valid注解或者@Validated進行參數(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ù)奶幚矸绞剑缬涗涘e誤日志,并返回合適的錯誤響應(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 進行數(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ù)雜的項目中更容易處理特定的異常情況,同時提供更詳細的錯誤信息。然而,局部異常處理可能帶來代碼冗余和維護難度的問題,特別是在大型項目中。
在實際應(yīng)用中,選擇全局異常處理還是局部異常處理應(yīng)根據(jù)項目規(guī)模和需求進行權(quán)衡。對于小型項目或簡單場景,全局異常處理可能是一種更簡單、合適的選擇。而對于大型項目或需要個性化異常處理的復(fù)雜業(yè)務(wù)邏輯,局部異常處理則提供了更為靈活的方案。最佳實踐是在項目中根據(jù)具體情況靈活使用這兩種方式,以平衡一致性和個性化需求。
最佳實踐與注意事項
1. 最佳實踐
- 統(tǒng)一響應(yīng)格式: 在異常處理中,使用統(tǒng)一的響應(yīng)格式有助于客戶端更容易理解和處理錯誤。通常,返回一個包含錯誤碼、錯誤信息和可能的詳細信息的響應(yīng)對象。
- 詳細錯誤日志: 在異常處理中記錄詳細的錯誤日志,包括異常類型、發(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)致性能問題和代碼可讀性降低。
- 不忽略異常: 避免在異常處理中忽略異?;騼H僅打印日志而不進行適當(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)用的健壯性、可讀性和可維護性。全局異常處理和局部異常處理各有優(yōu)劣,需要根據(jù)項目的規(guī)模和需求來靈活選擇。通過采用統(tǒng)一的響應(yīng)格式、詳細的錯誤日志、適當(dāng)?shù)腍TTP狀態(tài)碼等最佳實踐,可以使異常處理更為有效和易于管理。同時,注意避免濫用異常、忽略異常、適時拋出異常等注意事項,有助于確保異常處理的質(zhì)量。在開發(fā)過程中,持續(xù)關(guān)注和優(yōu)化異常處理,將有助于提高應(yīng)用的穩(wěn)定性和用戶體驗。
以上就是詳解SpringBoot中的統(tǒng)一結(jié)果返回與統(tǒng)一異常處理的詳細內(nèi)容,更多關(guān)于SpringBoot統(tǒng)一結(jié)果返回與統(tǒng)一異常處理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springBoot整合CXF并實現(xiàn)用戶名密碼校驗的方法
這篇文章主要介紹了springBoot整合CXF并實現(xiàn)用戶名密碼校驗的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
Java 處理高并發(fā)負載類優(yōu)化方法案例詳解
這篇文章主要介紹了Java 處理高并發(fā)負載類優(yōu)化方法案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08
解讀HttpServletRequestWrapper處理request數(shù)據(jù)流多次讀取問題
在Java Web開發(fā)中,獲取HTTP請求參數(shù)是常見需求,本文詳細討論了通過POST方式獲取參數(shù)的兩種主要方法:使用request.getParameter()適用于application/x-www-form-urlencoded和multipart/form-data內(nèi)容類型;而對于application/json類型的數(shù)據(jù)2024-10-10
Java使用Collections.sort對中文進行排序方式
這篇文章主要介紹了Java使用Collections.sort對中文進行排序方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
IDEA2020.1創(chuàng)建springboot項目(國內(nèi)腳手架)安裝lombok
這篇文章主要介紹了IDEA2020.1創(chuàng)建springboot項目(國內(nèi)腳手架)安裝lombok,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

