"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">



對(duì)于我們的業(yè)務(wù)應(yīng)用,應(yīng)該提供更詳細(xì)的有關(guān)業(yè)務(wù)的錯(cuò)誤信息

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">


在設(shè)計(jì)REST API的響應(yīng)時(shí),需要理解以下重點(diǎn):

1. status表示HTTP狀態(tài)代碼。
2. error_code表示REST API特定的錯(cuò)誤代碼。此字段有助于傳遞API /業(yè)務(wù)領(lǐng)域中特定信息。比如類似Oracle錯(cuò)誤ORA-12345
3. message字段表示人類可讀的錯(cuò)誤消息。國際化信息
4. details部分表示完整詳細(xì)信息。
5. information_link字段指定有關(guān)錯(cuò)誤或異常的詳細(xì)信息的鏈接。

Spring REST錯(cuò)誤處理


Spring和Spring Boot提供了許多錯(cuò)誤/異常處理選項(xiàng)。比如 @ExceptionHandler注釋,@ExceptionHandler是一個(gè)Spring注釋,以處理請(qǐng)求引發(fā)的異常。此注釋在@Controller級(jí)別上起作用。

@RestController
public class WelcomeController {

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

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


該方法存在幾個(gè)個(gè)問題或缺點(diǎn):

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

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

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

由于存在所有這些限制,因此建議不要在構(gòu)建RESTful API時(shí)使用此方法

Spring的異常處理


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

@ControllerAdvice
public class GlobalRestExceptionHandler extends ResponseEntityExceptionHandler {

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


通過@ControllerAdvice和@ExceptionHandler組合,能夠統(tǒng)一跨所有@RequestMapping方法實(shí)現(xiàn)集中式異常處理。這是在使用基于Spring的REST API時(shí)的一種便捷方式,因?yàn)榭梢灾付≧esponseEntity為返回值。

現(xiàn)在我們可以定義一下我們的錯(cuò)誤類信息的代碼,然后把這個(gè)對(duì)象嵌入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類中每個(gè)字段的含義等同于前面JSON格式的錯(cuò)誤信息含義。

下面我們看看幾種常見的客戶端請(qǐng)求錯(cuò)誤場(chǎng)景下如何使用這個(gè)ApiErrorResponse類:

(1)當(dāng)方法參數(shù)不是預(yù)期類型時(shí),拋出MethodArgumentTypeMismatchException異常,我們構(gòu)造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),當(dāng)API無法讀取HTTP消息時(shí),拋出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調(diào)用的響應(yīng)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。

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

@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">


這里不會(huì)詳細(xì)介紹如何在REST API中處理一個(gè)個(gè)不同的異常,因?yàn)樗挟惓6伎梢园凑丈厦娣绞竭M(jìn)行類似方式處理。下面是REST API中一些常見異常的類可以提供參考:

HttpMediaTypeNotSupportedException
HttpRequestMethodNotSupportedException
TypeMismatchException


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

@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提供了許多構(gòu)建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">


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

spring.mvc.throw-exception-if-no-handler-found=true
# 如果處理一個(gè)請(qǐng)求發(fā)生異常沒有異常處理器時(shí),決定”NoHandlerFoundException”是否拋出
<p class=”indent”>

概要


在Spring基礎(chǔ)REST API中正確處理和處理異常非常重要。在這篇文章中,我們介紹了實(shí)現(xiàn)Spring REST異常處理的不同選項(xiàng)。

為REST API構(gòu)建一個(gè)良好的異常處理工作流是一個(gè)迭代和復(fù)雜的過程。一個(gè)好的異常處理機(jī)制允許API客戶端知道請(qǐng)求出了什么問題。

文章轉(zhuǎn)自微信公眾號(hào)@Linyb極客之路

上一篇:

Flask-RESTful:最強(qiáng)Python Web服務(wù)框架,輕松構(gòu)建REST API

下一篇:

淺談四種API設(shè)計(jì)風(fēng)格(RPC、REST、GraphQL、服務(wù)端驅(qū)動(dòng))
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊(cè)

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門場(chǎng)景實(shí)測(cè),選對(duì)API

#AI文本生成大模型API

對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對(duì)比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)