摘要:在本講中,通過一個精簡的項目,著重介紹一些的異常處理技巧?,F(xiàn)在,為了快熟實現(xiàn)自定義異常信息處理類,并讓其正常工作,我們可以直接擴展提供的類來定義用戶異常信息處理類。將異常報告封裝到對象中,并回傳給。
能夠正確的處理REST API程序拋出的異常以及返回友好的異常信息是一件非常重要的事情,因為它可以幫助API客戶端正確的對服務(wù)端的問題作出正確的響應(yīng)。這有助于提高REST API的服務(wù)質(zhì)量。Spring Boot默認返回的異常信息對于API客戶端來說是晦澀難懂的,只有開發(fā)者才會關(guān)注那些堆棧異常報告。在本講中,將對如何處理好Spring REST API異常信息做一個梳理。
最近一段時間,Spring Boot成為了Java開發(fā)圈子的網(wǎng)紅,越來越多的開發(fā)者選擇Spring Boot來構(gòu)建REST API。使用Spring Boot,能夠幫助開發(fā)者減少模板代碼和配置文件的編寫工作量。Spring Boot開箱即用的特性,受到廣大開發(fā)者的熱寵。在本講中,通過一個精簡的Demo項目,著重介紹一些Spring Boot REST API的異常處理技巧。
如果你不想閱讀本次內(nèi)容,只是想快速獲得相關(guān)的源碼,那你可以直接跳轉(zhuǎn)到文章的結(jié)尾,找到Gihub倉庫鏈接,通過該鏈接,你可以輕松的獲得本次內(nèi)容的全部源碼。
1. 定義明確的異常信息
當程序發(fā)送錯誤時,不應(yīng)該將晦澀的堆棧報告信息返回給API客戶端,從某種意義將,這是一種不禮貌的和不負責任的行為。現(xiàn)在,我們將模擬這樣一個需求,API客戶端可以向服務(wù)端發(fā)送請求以獲取一個或者多個用戶信息,同時還可以發(fā)送請求創(chuàng)建一個新的用戶信息。下面是大致的一個API信息:
API 名稱 | 說明 |
---|---|
GET /users/{userId} | 根據(jù)用戶ID檢索用戶信息,如果沒有找到,則返回用戶未找到異常信息 |
GET /users | 根據(jù)傳入的ID集合,檢索用戶信息,若未找到,返回未找到用戶異常信息 |
POST /users | 創(chuàng)建一個新的用戶 |
Spring MVC為我們提供了一些很有用的功能,以幫助我們解決系統(tǒng)的異常信息,并將有用的提示信息返回給API客戶端。
以 POST /users 創(chuàng)建一個新用戶為例,當我們提供正常的用戶數(shù)據(jù)并請求此接口時,REST API將返回如下的提示信息:
{ "id": 2, "username": "wukong", "age": 52, "height": 170 }
現(xiàn)在,將用戶年齡修改為200歲,身高修改為500厘米,用戶名為rulai ,在此請求此REST API,觀察API的返回信息:
{ "restapierror": { "status": "BAD_REQUEST", "timestamp": "2019-05-19 06:04:47", "message": "Validation error", "subErrors": [ { "object": "user", "field": "height", "rejectedValue": 500, "message": "用戶身高不能超過250厘米" }, { "object": "user", "field": "age", "rejectedValue": 200, "message": "用戶年齡不能超過120歲" } ] } }
如上所示,當API客戶端提供不正確的數(shù)據(jù)時,REST API返回了格式良好的異常提示信息,timestamp由原來的整形時間戳格式化為一個可讀的日期+時間,同時還詳細列舉了詳細的錯誤報告。
2. 包裝異常信息
為了能夠提供一個可讀的JSON格式異常信息給API客戶端,我們需要在項目中引入Jackson JSR 310的依賴包,使用其提供的@JsonFormat注解將Java中的日期和時間按照我們給定的日期和時間模板進行格式化。現(xiàn)在,將下面的依賴包加入的Maven pom.xml文件中:
com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.9.8
依賴就緒后,我們需要提供一個異常信息的包裝類:RestApiError。它將負責對REST API拋出的異常信息進行封裝:
public class RestApiError { private HttpStatus status; @JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd hh:mm:ss") private LocalDateTime timestamp; private String message; private String debugMessage; private List
subErrors; private RestApiError(){ timestamp = LocalDateTime.now(); } RestApiError(HttpStatus status){ this(); this.status = status; } RestApiError(HttpStatus status,Throwable ex){ this(); this.status = status; this.message = "Unexpected error"; this.debugMessage = ex.getLocalizedMessage(); } RestApiError(HttpStatus status,String message,Throwable ex){ this(); this.status = status; this.message = message; this.debugMessage = ex.getLocalizedMessage(); } }
status 屬性用于記錄響應(yīng)狀態(tài)。它沿用了HttpStatus的所有狀態(tài)嗎,如4xx和5xx。
timestamp屬性用于記錄發(fā)送錯誤的時間
message屬性用于記錄自定義的異常消息,通常是對API客戶端友好的提示信息
debugMessage屬性用于記錄更為詳細的錯誤報告
subErrors屬性用于記錄異常附帶的子異常信息,如用戶實體中字段校驗信息等
RestApiSubError類用于記錄更為細致的異常信息,通常為實體類中字段校驗失敗的異常報告:
abstract class RestApiSubError{} @Data @EqualsAndHashCode(callSuper = false) @AllArgsConstructor class RestApiValidationError extends RestApiSubError{ private String object; private String field; private Object rejectedValue; private String message; RestApiValidationError(String object,String message){ this.object = object; this.message = message; } }
RestApiSubError類是一個抽象的空類,具體的擴展將在RestApiValidationError中進行實現(xiàn)。RestApiValidationError類將記錄實體類中(如本講中的User對象)屬性校驗失敗報告。
現(xiàn)在,我們來校驗GET /users/1 API,檢索用戶ID為1的用戶信息:
{ "id": 1, "username": "ramostear", "age": 28, "height": 170 }
REST API成功的返回了用戶信息,接下來我們傳入一個系統(tǒng)不存在的用戶ID,看看REST API返回什么信息:
GET /users/100
{ "restapierror": { "status": "NOT_FOUND", "timestamp": "2019-05-19 06:31:17", "message": "User was not found for parameters {id=100}" } }
通過上述的JSON信息我們可以看到,檢索不存在的用戶信息,REST API返回了友好的提示信息。在一開始的時候我們測試提供不合符規(guī)范的用戶年齡和身高信息,接下來我們在來測試一下提供一個空的用戶名,觀察REST API返回的信息:
{ "restapierror": { "status": "BAD_REQUEST", "timestamp": "2019-05-19 06:37:46", "message": "Validation error", "subErrors": [ { "object": "user", "field": "username", "rejectedValue": "", "message": "不能為空" } ] } }
3. Spring Boot 處理異常信息的流程
Spring Boot 處理REST API異常信息將會涉及到三個注解:
@RestController : 負責處理REST API具體操作邏輯的注解
@ExceptionHandler : 負責處理@RestController標注的類中拋出的異常的注解
@ControllerAdvice : 能夠?qū)ExceptionHandler標注的方法集中到一個地方進行處理的注解
@ControllerAdivice注解是在Spring 3.2版本中新增的一個注解,它能夠?qū)蝹€由@ExceptionHandler注解標注的方法應(yīng)用到多個控制器中。使用它的好處是我們可以在一個統(tǒng)一的地方同時處理多個控制器拋出的異常,當控制器有異常拋出時,ControllerAdvice會根據(jù)當前拋出的異常類型,自動匹配對應(yīng)的ExceptionHandler;當沒有特定的Exception可用時,將調(diào)用默認的異常信息處理類來處理控制器拋出的異常(默認的異常信息處理類)。
下面,我們通過一張流程示例圖,更為直觀的了解Spring Application處理控制器異常信息的全部過程:
在圖中,藍色箭頭表示正常的請求和響應(yīng)過程,紅色箭頭表示發(fā)生異常的請求和響應(yīng)過程。
4. 自定義異常信息處理類
Spring Framework自帶的異常信息處理類往往不能滿足我們實際的業(yè)務(wù)需求,這就需要我們定義符合具體情況的異常信息處理類,在自定義異常信息處理類中,我們可以封裝更為詳細的異常報告。
自定義異常信息處理類,我們可以站在“巨人”的肩膀上,快速封裝自己的異常信息處理類,而不必要從頭開始造“輪子”?,F(xiàn)在,為了快熟實現(xiàn)自定義異常信息處理類,并讓其正常工作,我們可以直接擴展Spring 提供的ResponseEntityExceptionHandler類來定義用戶異常信息處理類。ResponseEntityExceptionHandler已經(jīng)提供了很多可用的功能,我們只需要擴展該類或者覆蓋其提供的方法即可。
打開ResponseEntityExceptionHandler類,我們可以看到如下的源碼:
public abstract class ResponseEntityExceptionHandler { //不支持的HTTP請求方法異常信息處理方法 protected ResponseEntity
我們選擇性的覆蓋幾個常用的異常處理方法,并添加我們自定義異常處理方法:
public class RestExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(UserNotFoundException.class) protected ResponseEntity
handleUserNotFound(UserNotFoundException ex){ RestApiError apiError = new RestApiError(HttpStatus.NOT_FOUND); apiError.setMessage(ex.getMessage()); return buildResponseEntity(apiError); } @Override protected ResponseEntity handleMissingServletRequestParameter( MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { String error = ex.getParameterName() + " parameter is missing"; return buildResponseEntity(new RestApiError(BAD_REQUEST, error, ex)); } @Override protected ResponseEntity handleHttpMediaTypeNotSupported( HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { StringBuilder builder = new StringBuilder(); builder.append(ex.getContentType()); builder.append(" media type is not supported. Supported media types are "); ex.getSupportedMediaTypes().forEach(t -> builder.append(t).append(", ")); return buildResponseEntity(new RestApiError(HttpStatus.UNSUPPORTED_MEDIA_TYPE, builder.substring(0, builder.length() - 2), ex)); } ... }
UserNotFoundException類為我們自定義異常信息類,在執(zhí)行GET /users/{userIds}或 GET /users請求時,如果數(shù)據(jù)庫中不存在該ID的記錄信息,將拋出UserNotFoundException異常信息,且將響應(yīng)狀態(tài)碼設(shè)置為NOT_FOUND。UserNotFoundException源碼如下:
public class UserNotFoundException extends Exception { public UserNotFoundException(Class clz,String...searchParams){ super(UserNotFoundException.generateMessage(clz.getSimpleName(),toMap(String.class,String.class,searchParams))); } private static String generateMessage(String entity, Map
searchParams){ return StringUtils.capitalize(entity)+ " was not found for parameters "+ searchParams; } private static Map toMap(Class key,Class value,Object...entries){ if(entries.length % 2 == 1){ throw new IllegalArgumentException("Invalid entries"); } return IntStream.range(0,entries.length/2).map(i->i*2) .collect(HashMap::new, (m,i)->m.put(key.cast(entries[i]),value.cast(entries[i+1])),Map::putAll); } }
下圖將更為直觀的說明自定義異常處理的整個流程:
當UserService發(fā)生異常時,異常信息將向上傳遞到UserController,此時的異常信息被Spring所捕獲,并將其跳轉(zhuǎn)到UserNotFoundException處理方法中。UserNotFoundException將異常報告封裝到RestApiError對象中,并回傳給API Client。通過此方法,API客戶端將獲得一份邏輯清晰的響應(yīng)報告。
本次課程的全部源碼已經(jīng)上傳到 Github 倉庫,你可以點擊此鏈接獲取源碼:github.com/ramostear/S…
原文地址:www.ramostear.com/articles/sp…
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/6704.html
摘要:指南無論你正在構(gòu)建什么,這些指南都旨在讓你盡快提高工作效率使用團隊推薦的最新項目版本和技術(shù)。使用進行消息傳遞了解如何將用作消息代理。安全架構(gòu)的主題指南,這些位如何組合以及它們?nèi)绾闻c交互。使用的主題指南以及如何為應(yīng)用程序創(chuàng)建容器鏡像。 Spring 指南 無論你正在構(gòu)建什么,這些指南都旨在讓你盡快提高工作效率 — 使用Spring團隊推薦的最新Spring項目版本和技術(shù)。 入門指南 這些...
摘要:挺多人咨詢的,異常處理用切面注解去實現(xiàn)去全局異常處理。全局異常處理類,代碼如下代碼解析如下抽象類是用來處理全局錯誤時進行擴展和實現(xiàn)注解標記的切面排序,值越小擁有越高的優(yōu)先級,這里設(shè)置優(yōu)先級偏高。 本文內(nèi)容 為什么要全局異常處理? WebFlux REST 全局異常處理實戰(zhàn) 小結(jié) 摘錄:只有不斷培養(yǎng)好習慣,同時不斷打破壞習慣,我們的行為舉止才能夠自始至終都是正確的。 一、為什么要全局...
摘要:下一代服務(wù)端開發(fā)下一代服務(wù)端開發(fā)第部門快速開始第章快速開始環(huán)境準備,,快速上手實現(xiàn)一個第章企業(yè)級服務(wù)開發(fā)從到語言的缺點發(fā)展歷程的缺點為什么是產(chǎn)生的背景解決了哪些問題為什么是的發(fā)展歷程容器的配置地獄是什么從到下一代企業(yè)級服務(wù)開發(fā)在移動開發(fā)領(lǐng)域 《 Kotlin + Spring Boot : 下一代 Java 服務(wù)端開發(fā) 》 Kotlin + Spring Boot : 下一代 Java...
摘要:為所有實例進行應(yīng)用程序級的附加定制,你可以聲明并在注入點局部的更改。最后,你可以回到原來的并使用,在這種情況下,不應(yīng)用自動配置或。上一篇使用調(diào)用服務(wù)下一篇驗證發(fā)送電子郵件 34. 使用WebClient調(diào)用REST服務(wù) 如果你的classpath上有Spring WebFlux,那么你還可以選擇使用WebClient來調(diào)用遠程REST服務(wù),與RestTemplate相比,這個客戶端具有...
摘要:來源是最流行的用于開發(fā)微服務(wù)的框架。以下依次列出了最佳實踐,排名不分先后。這非常有助于避免可怕的地獄。推薦使用構(gòu)造函數(shù)注入這一條實踐來自的項目負責人。保持業(yè)務(wù)邏輯免受代碼侵入的一種方法是使用構(gòu)造函數(shù)注入。 showImg(https://mmbiz.qpic.cn/mmbiz_jpg/R3InYSAIZkHQ40ly9Oztiart2lESCyjCH0JwFRp3oErlYobhibM...
閱讀 1837·2021-09-23 11:21
閱讀 698·2019-08-30 15:55
閱讀 832·2019-08-29 15:40
閱讀 528·2019-08-29 12:56
閱讀 3158·2019-08-26 12:00
閱讀 3552·2019-08-23 18:24
閱讀 2246·2019-08-23 17:08
閱讀 1637·2019-08-23 17:03