摘要:在本講中,通過(guò)一個(gè)精簡(jiǎn)的項(xiàng)目,著重介紹一些的異常處理技巧?,F(xiàn)在,為了快熟實(shí)現(xiàn)自定義異常信息處理類,并讓其正常工作,我們可以直接擴(kuò)展提供的類來(lái)定義用戶異常信息處理類。將異常報(bào)告封裝到對(duì)象中,并回傳給。
能夠正確的處理REST API程序拋出的異常以及返回友好的異常信息是一件非常重要的事情,因?yàn)樗梢詭椭鶤PI客戶端正確的對(duì)服務(wù)端的問(wèn)題作出正確的響應(yīng)。這有助于提高REST API的服務(wù)質(zhì)量。Spring Boot默認(rèn)返回的異常信息對(duì)于API客戶端來(lái)說(shuō)是晦澀難懂的,只有開(kāi)發(fā)者才會(huì)關(guān)注那些堆棧異常報(bào)告。在本講中,將對(duì)如何處理好Spring REST API異常信息做一個(gè)梳理。
最近一段時(shí)間,Spring Boot成為了Java開(kāi)發(fā)圈子的網(wǎng)紅,越來(lái)越多的開(kāi)發(fā)者選擇Spring Boot來(lái)構(gòu)建REST API。使用Spring Boot,能夠幫助開(kāi)發(fā)者減少模板代碼和配置文件的編寫(xiě)工作量。Spring Boot開(kāi)箱即用的特性,受到廣大開(kāi)發(fā)者的熱寵。在本講中,通過(guò)一個(gè)精簡(jiǎn)的Demo項(xiàng)目,著重介紹一些Spring Boot REST API的異常處理技巧。
如果你不想閱讀本次內(nèi)容,只是想快速獲得相關(guān)的源碼,那你可以直接跳轉(zhuǎn)到文章的結(jié)尾,找到Gihub倉(cāng)庫(kù)鏈接,通過(guò)該鏈接,你可以輕松的獲得本次內(nèi)容的全部源碼。
1. 定義明確的異常信息
當(dāng)程序發(fā)送錯(cuò)誤時(shí),不應(yīng)該將晦澀的堆棧報(bào)告信息返回給API客戶端,從某種意義將,這是一種不禮貌的和不負(fù)責(zé)任的行為?,F(xiàn)在,我們將模擬這樣一個(gè)需求,API客戶端可以向服務(wù)端發(fā)送請(qǐng)求以獲取一個(gè)或者多個(gè)用戶信息,同時(shí)還可以發(fā)送請(qǐng)求創(chuàng)建一個(gè)新的用戶信息。下面是大致的一個(gè)API信息:
API 名稱 | 說(shuō)明 |
---|---|
GET /users/{userId} | 根據(jù)用戶ID檢索用戶信息,如果沒(méi)有找到,則返回用戶未找到異常信息 |
GET /users | 根據(jù)傳入的ID集合,檢索用戶信息,若未找到,返回未找到用戶異常信息 |
POST /users | 創(chuàng)建一個(gè)新的用戶 |
Spring MVC為我們提供了一些很有用的功能,以幫助我們解決系統(tǒng)的異常信息,并將有用的提示信息返回給API客戶端。
以 POST /users 創(chuàng)建一個(gè)新用戶為例,當(dāng)我們提供正常的用戶數(shù)據(jù)并請(qǐng)求此接口時(shí),REST API將返回如下的提示信息:
{ "id": 2, "username": "wukong", "age": 52, "height": 170 }
現(xiàn)在,將用戶年齡修改為200歲,身高修改為500厘米,用戶名為rulai ,在此請(qǐng)求此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": "用戶身高不能超過(guò)250厘米" }, { "object": "user", "field": "age", "rejectedValue": 200, "message": "用戶年齡不能超過(guò)120歲" } ] } }
如上所示,當(dāng)API客戶端提供不正確的數(shù)據(jù)時(shí),REST API返回了格式良好的異常提示信息,timestamp由原來(lái)的整形時(shí)間戳格式化為一個(gè)可讀的日期+時(shí)間,同時(shí)還詳細(xì)列舉了詳細(xì)的錯(cuò)誤報(bào)告。
2. 包裝異常信息
為了能夠提供一個(gè)可讀的JSON格式異常信息給API客戶端,我們需要在項(xiàng)目中引入Jackson JSR 310的依賴包,使用其提供的@JsonFormat注解將Java中的日期和時(shí)間按照我們給定的日期和時(shí)間模板進(jìn)行格式化。現(xiàn)在,將下面的依賴包加入的Maven pom.xml文件中:
com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.9.8
依賴就緒后,我們需要提供一個(gè)異常信息的包裝類:RestApiError。它將負(fù)責(zé)對(duì)REST API拋出的異常信息進(jìn)行封裝:
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ā)送錯(cuò)誤的時(shí)間
message屬性用于記錄自定義的異常消息,通常是對(duì)API客戶端友好的提示信息
debugMessage屬性用于記錄更為詳細(xì)的錯(cuò)誤報(bào)告
subErrors屬性用于記錄異常附帶的子異常信息,如用戶實(shí)體中字段校驗(yàn)信息等
RestApiSubError類用于記錄更為細(xì)致的異常信息,通常為實(shí)體類中字段校驗(yàn)失敗的異常報(bào)告:
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類是一個(gè)抽象的空類,具體的擴(kuò)展將在RestApiValidationError中進(jìn)行實(shí)現(xiàn)。RestApiValidationError類將記錄實(shí)體類中(如本講中的User對(duì)象)屬性校驗(yàn)失敗報(bào)告。
現(xiàn)在,我們來(lái)校驗(yàn)GET /users/1 API,檢索用戶ID為1的用戶信息:
{ "id": 1, "username": "ramostear", "age": 28, "height": 170 }
REST API成功的返回了用戶信息,接下來(lái)我們傳入一個(gè)系統(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}" } }
通過(guò)上述的JSON信息我們可以看到,檢索不存在的用戶信息,REST API返回了友好的提示信息。在一開(kāi)始的時(shí)候我們測(cè)試提供不合符規(guī)范的用戶年齡和身高信息,接下來(lái)我們?cè)趤?lái)測(cè)試一下提供一個(gè)空的用戶名,觀察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異常信息將會(huì)涉及到三個(gè)注解:
@RestController : 負(fù)責(zé)處理REST API具體操作邏輯的注解
@ExceptionHandler : 負(fù)責(zé)處理@RestController標(biāo)注的類中拋出的異常的注解
@ControllerAdvice : 能夠?qū)ExceptionHandler標(biāo)注的方法集中到一個(gè)地方進(jìn)行處理的注解
@ControllerAdivice注解是在Spring 3.2版本中新增的一個(gè)注解,它能夠?qū)蝹€(gè)由@ExceptionHandler注解標(biāo)注的方法應(yīng)用到多個(gè)控制器中。使用它的好處是我們可以在一個(gè)統(tǒng)一的地方同時(shí)處理多個(gè)控制器拋出的異常,當(dāng)控制器有異常拋出時(shí),ControllerAdvice會(huì)根據(jù)當(dāng)前拋出的異常類型,自動(dòng)匹配對(duì)應(yīng)的ExceptionHandler;當(dāng)沒(méi)有特定的Exception可用時(shí),將調(diào)用默認(rèn)的異常信息處理類來(lái)處理控制器拋出的異常(默認(rèn)的異常信息處理類)。
下面,我們通過(guò)一張流程示例圖,更為直觀的了解Spring Application處理控制器異常信息的全部過(guò)程:
在圖中,藍(lán)色箭頭表示正常的請(qǐng)求和響應(yīng)過(guò)程,紅色箭頭表示發(fā)生異常的請(qǐng)求和響應(yīng)過(guò)程。
4. 自定義異常信息處理類
Spring Framework自帶的異常信息處理類往往不能滿足我們實(shí)際的業(yè)務(wù)需求,這就需要我們定義符合具體情況的異常信息處理類,在自定義異常信息處理類中,我們可以封裝更為詳細(xì)的異常報(bào)告。
自定義異常信息處理類,我們可以站在“巨人”的肩膀上,快速封裝自己的異常信息處理類,而不必要從頭開(kāi)始造“輪子”?,F(xiàn)在,為了快熟實(shí)現(xiàn)自定義異常信息處理類,并讓其正常工作,我們可以直接擴(kuò)展Spring 提供的ResponseEntityExceptionHandler類來(lái)定義用戶異常信息處理類。ResponseEntityExceptionHandler已經(jīng)提供了很多可用的功能,我們只需要擴(kuò)展該類或者覆蓋其提供的方法即可。
打開(kāi)ResponseEntityExceptionHandler類,我們可以看到如下的源碼:
public abstract class ResponseEntityExceptionHandler { //不支持的HTTP請(qǐng)求方法異常信息處理方法 protected ResponseEntity
我們選擇性的覆蓋幾個(gè)常用的異常處理方法,并添加我們自定義異常處理方法:
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請(qǐng)求時(shí),如果數(shù)據(jù)庫(kù)中不存在該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); } }
下圖將更為直觀的說(shuō)明自定義異常處理的整個(gè)流程:
當(dāng)UserService發(fā)生異常時(shí),異常信息將向上傳遞到UserController,此時(shí)的異常信息被Spring所捕獲,并將其跳轉(zhuǎn)到UserNotFoundException處理方法中。UserNotFoundException將異常報(bào)告封裝到RestApiError對(duì)象中,并回傳給API Client。通過(guò)此方法,API客戶端將獲得一份邏輯清晰的響應(yīng)報(bào)告。
本次課程的全部源碼已經(jīng)上傳到 Github 倉(cāng)庫(kù),你可以點(diǎn)擊此鏈接獲取源碼:github.com/ramostear/S…
原文地址:www.ramostear.com/articles/sp…
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/6704.html
摘要:指南無(wú)論你正在構(gòu)建什么,這些指南都旨在讓你盡快提高工作效率使用團(tuán)隊(duì)推薦的最新項(xiàng)目版本和技術(shù)。使用進(jìn)行消息傳遞了解如何將用作消息代理。安全架構(gòu)的主題指南,這些位如何組合以及它們?nèi)绾闻c交互。使用的主題指南以及如何為應(yīng)用程序創(chuàng)建容器鏡像。 Spring 指南 無(wú)論你正在構(gòu)建什么,這些指南都旨在讓你盡快提高工作效率 — 使用Spring團(tuán)隊(duì)推薦的最新Spring項(xiàng)目版本和技術(shù)。 入門(mén)指南 這些...
摘要:挺多人咨詢的,異常處理用切面注解去實(shí)現(xiàn)去全局異常處理。全局異常處理類,代碼如下代碼解析如下抽象類是用來(lái)處理全局錯(cuò)誤時(shí)進(jìn)行擴(kuò)展和實(shí)現(xiàn)注解標(biāo)記的切面排序,值越小擁有越高的優(yōu)先級(jí),這里設(shè)置優(yōu)先級(jí)偏高。 本文內(nèi)容 為什么要全局異常處理? WebFlux REST 全局異常處理實(shí)戰(zhàn) 小結(jié) 摘錄:只有不斷培養(yǎng)好習(xí)慣,同時(shí)不斷打破壞習(xí)慣,我們的行為舉止才能夠自始至終都是正確的。 一、為什么要全局...
摘要:下一代服務(wù)端開(kāi)發(fā)下一代服務(wù)端開(kāi)發(fā)第部門(mén)快速開(kāi)始第章快速開(kāi)始環(huán)境準(zhǔn)備,,快速上手實(shí)現(xiàn)一個(gè)第章企業(yè)級(jí)服務(wù)開(kāi)發(fā)從到語(yǔ)言的缺點(diǎn)發(fā)展歷程的缺點(diǎn)為什么是產(chǎn)生的背景解決了哪些問(wèn)題為什么是的發(fā)展歷程容器的配置地獄是什么從到下一代企業(yè)級(jí)服務(wù)開(kāi)發(fā)在移動(dòng)開(kāi)發(fā)領(lǐng)域 《 Kotlin + Spring Boot : 下一代 Java 服務(wù)端開(kāi)發(fā) 》 Kotlin + Spring Boot : 下一代 Java...
摘要:為所有實(shí)例進(jìn)行應(yīng)用程序級(jí)的附加定制,你可以聲明并在注入點(diǎn)局部的更改。最后,你可以回到原來(lái)的并使用,在這種情況下,不應(yīng)用自動(dòng)配置或。上一篇使用調(diào)用服務(wù)下一篇驗(yàn)證發(fā)送電子郵件 34. 使用WebClient調(diào)用REST服務(wù) 如果你的classpath上有Spring WebFlux,那么你還可以選擇使用WebClient來(lái)調(diào)用遠(yuǎn)程REST服務(wù),與RestTemplate相比,這個(gè)客戶端具有...
摘要:來(lái)源是最流行的用于開(kāi)發(fā)微服務(wù)的框架。以下依次列出了最佳實(shí)踐,排名不分先后。這非常有助于避免可怕的地獄。推薦使用構(gòu)造函數(shù)注入這一條實(shí)踐來(lái)自的項(xiàng)目負(fù)責(zé)人。保持業(yè)務(wù)邏輯免受代碼侵入的一種方法是使用構(gòu)造函數(shù)注入。 showImg(https://mmbiz.qpic.cn/mmbiz_jpg/R3InYSAIZkHQ40ly9Oztiart2lESCyjCH0JwFRp3oErlYobhibM...
閱讀 1926·2021-09-23 11:21
閱讀 758·2019-08-30 15:55
閱讀 904·2019-08-29 15:40
閱讀 634·2019-08-29 12:56
閱讀 3235·2019-08-26 12:00
閱讀 3633·2019-08-23 18:24
閱讀 2313·2019-08-23 17:08
閱讀 1719·2019-08-23 17:03