"timestamp": 1530772698787,
"status": 405,
"error": "Method Not Allowed",
"exception": "org.springframework.web.HttpRequestMethodNotSupportedException",
"message": "Request method 'POST' not supported",
"path": "/api/producer"
}
<p class="indent">



對于我們的業務應用,應該提供更詳細的有關業務的錯誤信息

HTTP/1.1  404
Content-Type: application/json
{
"status": 404,
"error_code": 123,
"message": "Oops! It looks like that file does not exist.",
"details": "File resource does not exist.We are working on fixing this issue.",
"information_link": "/api/producer"
}
<p class="indent">


在設計REST API的響應時,需要理解以下重點:

1. status表示HTTP狀態代碼。
2. error_code表示REST API特定的錯誤代碼。此字段有助于傳遞API /業務領域中特定信息。比如類似Oracle錯誤ORA-12345
3. message字段表示人類可讀的錯誤消息。國際化信息
4. details部分表示完整詳細信息。
5. information_link字段指定有關錯誤或異常的詳細信息的鏈接。

Spring REST錯誤處理


Spring和Spring Boot提供了許多錯誤/異常處理選項。比如 @ExceptionHandler注釋,@ExceptionHandler是一個Spring注釋,以處理請求引發的異常。此注釋在@Controller級別上起作用。

@RestController
public class WelcomeController {

@GetMapping("/greeting")
String greeting() throws Exception {
//
}

@ExceptionHandler({Exception.class})
public handleException(){
//
}
}
<p class="indent">


該方法存在幾個個問題或缺點:

(1)此注釋僅對指定的控制器Controller有效。
(2)這個注釋不是全局的,我們需要添加到每個控制器(不是很方便)。

大多數企業應用程序都是需要擴展Spring基類的控制器(也就是通用控制器)。我們可以將@ExceptionHandler加入基類控制器,來客服上面的不便和限制,但是有以下新問題:

(1)基類控制器不適用于所有類型的控制器。我們還是需要復制代碼。
(2)程序員編寫的控制器可能擴展不受我們控制的第三方面控制器類。

由于存在所有這些限制,因此建議不要在構建RESTful API時使用此方法

Spring的異常處理


Spring 3.2引入了@ControllerAdvice這個支持全局異常處理程序機制的注釋。@ControllerAdvice可以讓我們使用和上面完全相同的異常處理技術,但它是應用于整個應用程序,而不僅僅是某個控制器。看看下面的應用代碼:

@ControllerAdvice
public class GlobalRestExceptionHandler extends ResponseEntityExceptionHandler {

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(Exception.class)
public void defaultExceptionHandler() {
// Nothing to do
}
}
<p class="indent">


通過@ControllerAdvice和@ExceptionHandler組合,能夠統一跨所有@RequestMapping方法實現集中式異常處理。這是在使用基于Spring的REST API時的一種便捷方式,因為可以指定ResponseEntity為返回值。

現在我們可以定義一下我們的錯誤類信息的代碼,然后把這個對象嵌入ResponseEntity中返回。

public class ApiErrorResponse {

private HttpStatus status;
private String error_code;
private String message;
private String detail;

// getter and setters
//Builder
public static final class ApiErrorResponseBuilder {
private HttpStatus status;
private String error_code;
private String message;
private String detail;

private ApiErrorResponseBuilder() {
}

public static ApiErrorResponseBuilder anApiErrorResponse() {
return new ApiErrorResponseBuilder();
}

public ApiErrorResponseBuilder withStatus(HttpStatus status) {
this.status = status;
return this;
}

public ApiErrorResponseBuilder withError_code(String error_code) {
this.error_code = error_code;
return this;
}

public ApiErrorResponseBuilder withMessage(String message) {
this.message = message;
return this;
}

public ApiErrorResponseBuilder withDetail(String detail) {
this.detail = detail;
return this;
}

public ApiErrorResponse build() {
ApiErrorResponse apiErrorResponse = new ApiErrorResponse();
apiErrorResponse.status = this.status;
apiErrorResponse.error_code = this.error_code;
apiErrorResponse.detail = this.detail;
apiErrorResponse.message = this.message;
return apiErrorResponse;
}
}
}
<p class="indent">


ApiErrorResponse類中每個字段的含義等同于前面JSON格式的錯誤信息含義。

下面我們看看幾種常見的客戶端請求錯誤場景下如何使用這個ApiErrorResponse類:

(1)當方法參數不是預期類型時,拋出MethodArgumentTypeMismatchException異常,我們構造ApiErrorResponse類嵌入ResponseEntity返回:

@ExceptionHandler({MethodArgumentTypeMismatchException.class})
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request){

ApiErrorResponse response =new ApiErrorResponse.ApiErrorResponseBuilder()
.withStatus(status)
.withError_code(status.BAD_REQUEST.name())
.withMessage(ex.getLocalizedMessage()).build();

return new ResponseEntity<>(response, response.getStatus());
}
<p class="indent">


(2),當API無法讀取HTTP消息時,拋出HttpMessageNotReadable異常

 @ExceptionHandler({HttpMessageNotReadableException.class})
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String error = "Malformed JSON request ";
ApiErrorResponse response = new ApiErrorResponse.ApiErrorResponseBuilder()
.withStatus(status)
.withError_code("BAD_DATA")
.withMessage(ex.getLocalizedMessage())
.withDetail(error + ex.getMessage()).build();
return new ResponseEntity<>(response, response.getStatus());
}
<p class="indent">


下面是我們可以看到REST調用的響應JSON:

{
"status": "BAD_REQUEST",
"error_code": "BAD_DATA",
"message": "JSON parse error: Unexpected character
"detail": "Malformed JSON request JSON parse error: Unexpected character ('<' (code 60)): expected a valid value (number, String, array, object, 'true', 'false' or 'null');
}
<p class="indent">


(3)處理自定義異常,將自定義異常返回給客戶端API

看一個簡單的用例,當客戶端API通過其唯一ID調用后端存儲庫查找記錄時,如果找不到該記錄,我們的存儲庫類會返回null或空對象,在這種情況下,即使找不到我們想要的資源記錄,API也會向客戶端返回http 200 (OK響應。 為了處理所有類似這樣的情況,我們創建了一個自定義異常,并在全局異常處理器GlobalRestExceptionHandler中實現。

@ExceptionHandler(CustomServiceException.class)
protected ResponseEntity<Object> handleCustomAPIException(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {

ApiErrorResponse response =new ApiErrorResponse.ApiErrorResponseBuilder()
.withStatus(status)
.withError_code(HttpStatus.NOT_FOUND.name())
.withMessage(ex.getLocalizedMessage())
.withDetail(ex.getMessage())
.build();
return new ResponseEntity<>(response, response.getStatus());
}
<p class="indent">


這里不會詳細介紹如何在REST API中處理一個個不同的異常,因為所有異常都可以按照上面方式進行類似方式處理。下面是REST API中一些常見異常的類可以提供參考:

HttpMediaTypeNotSupportedException
HttpRequestMethodNotSupportedException
TypeMismatchException


(4)默認異常處理程序
既然我們無法處理系統中的所有異常。那么我們可以創建一個fallback異常處理器來作為沒有指定異常處理器的默認異常處理器。

@ControllerAdvice
public class GlobalRestExceptionHandler extends ResponseEntityExceptionHandler {

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(Exception.class)
public void defaultExceptionHandler() {
// Nothing to do
}
}
<p class="indent">

Spring Boot REST異常處理


Spring Boot提供了許多構建RESTful API的功能。Spring Boot 1.4引入了@RestControllerAdvice注釋,這樣可以更容易地處理異常。它和用@ControllerAdvice和@ResponseBody一樣方便:

@RestControllerAdvice
public class RestExceptionHandler {

@ExceptionHandler(CustomNotFoundException.class)
public ApiErrorResponse handleNotFoundException(CustomNotFoundException ex) {

ApiErrorResponse response =new ApiErrorResponse.ApiErrorResponseBuilder()
.withStatus(HttpStatus.NOT_FOUND)
.withError_code("NOT_FOUND")
.withMessage(ex.getLocalizedMessage()).build();

return responseMsg;
}
}
<p class="indent">


使用上述方法時,同時在Spring Boot的application.properties文件中將以下屬性設置為true

spring.mvc.throw-exception-if-no-handler-found=true
# 如果處理一個請求發生異常沒有異常處理器時,決定”NoHandlerFoundException”是否拋出
<p class=”indent”>

概要


在Spring基礎REST API中正確處理和處理異常非常重要。在這篇文章中,我們介紹了實現Spring REST異常處理的不同選項。

為REST API構建一個良好的異常處理工作流是一個迭代和復雜的過程。一個好的異常處理機制允許API客戶端知道請求出了什么問題。

文章轉自微信公眾號@Linyb極客之路

上一篇:

Flask-RESTful:最強Python Web服務框架,輕松構建REST API

下一篇:

淺談四種API設計風格(RPC、REST、GraphQL、服務端驅動)
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費