1. 前言
統(tǒng)一返回值封裝、統(tǒng)一異常處理和異常錯誤碼體系的意義在于提高代碼的可維護性和可讀性,使得代碼更加健壯和穩(wěn)定。統(tǒng)一返回值封裝可以避免每一個接口都需要手工拼裝響應(yīng)報文;統(tǒng)一異常處理可以將異常處理的邏輯集中到一個地方,避免代碼中出現(xiàn)大量的try-catch語句,降低了代碼的復(fù)雜度,提高了代碼的可讀性;異常體系的設(shè)計可以清晰地區(qū)分不同類型的異常,使得開發(fā)者能夠更加精準地處理異常情況,并且能夠更好地定位和解決問題。
Graceful Response是一個Spring Boot體系下的優(yōu)雅響應(yīng)處理組件,提供一站式統(tǒng)一返回值封裝、全局異常處理、自定義異常錯誤碼、自定義參數(shù)校驗異常碼等功能,使用Graceful Response進行web接口開發(fā)不僅可以節(jié)省大量的時間,還可以提高代碼質(zhì)量,使代碼邏輯更清晰。
強烈推薦你花3分鐘學(xué)會它!
Graceful Response的Github地址: https://github.com/feiniaojin/graceful-response ,歡迎star!
Graceful Response的案例工程代碼:https://github.com/feiniaojin/graceful-response-example.git
2. Spring Boot Web API接口數(shù)據(jù)返回的現(xiàn)狀
我們進行Spring Boo Web API接口開發(fā)時,通常大部分的Controller代碼是這樣的:
public class Controller {
@GetMapping("/query")
@ResponseBody
public Response query(Parameter params) {
Response res = new Response();
try {
//1.校驗params參數(shù),非空校驗、長度校驗
if (illegal(params)) {
res.setCode(1);
res.setMsg("error");
return res;
}
//2.調(diào)用Service的一系列操作
Data data = service.query(params);
//3.執(zhí)行正確時,將操作結(jié)果設(shè)置到res對象中
res.setData(data);
res.setCode(0);
res.setMsg("ok");
return res;
} catch (BizException1 e) {
//4.異常處理:一堆丑陋的try...catch,如果有錯誤碼的,還需要手工填充錯誤碼
res.setCode(1024);
res.setMsg("error");
return res;
} catch (BizException2 e) {
//4.異常處理:一堆丑陋的try...catch,如果有錯誤碼的,還需要手工填充錯誤碼
res.setCode(2048);
res.setMsg("error");
return res;
} catch (Exception e) {
//4.異常處理:一堆丑陋的try...catch,如果有錯誤碼的,還需要手工填充錯誤碼
res.setCode(1);
res.setMsg("error");
return res;
}
}
}
這段代碼存在什么問題呢?真正的業(yè)務(wù)邏輯被冗余代碼淹沒,可讀性太差。
真正執(zhí)行業(yè)務(wù)的代碼只有
Data data=service.query(params);
其他代碼不管是正常執(zhí)行還是異常處理,都是為了異常封裝、把結(jié)果封裝為特定的格式,例如以下格式:
{
"code": 0,
"msg": "ok",
"data": {
"id": 1,
"name": "username"
}
}
這樣的邏輯每個接口都需要處理一遍,都是繁瑣的重復(fù)勞動。
現(xiàn)在,只需要引入Graceful Response組件并通過@EnableGracefulResponse啟用,就可以直接返回業(yè)務(wù)結(jié)果并自動完成response的格式封裝。
以下是使用Graceful Response之后的代碼,實現(xiàn)同樣的返回值封裝、異常處理、異常錯誤碼功能,但可以看到代碼變得非常簡潔,可讀性非常強。
public class Controller {
@GetMapping("/query")
@ResponseBody
public Data query(Parameter params) {
return service.query(params);
}
}
3. 快速入門
3.1 引入maven依賴
graceful-response已發(fā)布至maven中央倉庫,可以直接引入到項目中,maven依賴如下:
com.feiniaojin/groupId?>
graceful-response/artifactId?>
{此處替換為最新的版本號}/version?>
/dependency?>
以下鏈接可以查看maven中央倉庫中最新的版本:
https://central.sonatype.com/artifact/com.feiniaojin/graceful-response/3.0/versions
3.2 在啟動類中引入@EnableGracefulResponse注解
@EnableGracefulResponse
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
3.3 Controller方法直接返回結(jié)果
?普通的查詢
@Controller
public class Controller {
@RequestMapping("/get")
@ResponseBody
public UserInfoView get(Long id) {
log.info("id={}", id);
return UserInfoView.builder().id(id).name("name" + id).build();
}
}
UserInfoView的源碼:
@Data
@Builder
public class UserInfoView {
private Long id;
private String name;
}
這個接口直接返回了 UserInfoView的實例對象,調(diào)用接口時,Graceful Response將自動封裝為以下格式:
{
"status": {
"code": "0",
"msg": "ok"
},
"payload": {
"id": 1,
"name": "name1"
}
}
可以看到UserInfoView被自動封裝到payload字段中。
Graceful Response提供了兩種風(fēng)格的Response,可以通過在application.properties文件中配置gr.responseStyle=1
,將以以下的格式進行返回:
{
"code": "0",
"msg": "ok",
"data": {
"id": 1,
"name": "name1"
}
}
如果這兩種風(fēng)格也不能滿足需要,我們還可以根據(jù)自己的需要進行自定義返回的Response格式。詳細見本文 4.3自定義Respnse格式。
?異常處理的場景
通過Graceful Response,我們不需要專門在Controller中處理異常,詳細見 4.1 Graceful Response異常錯誤碼處理。
?返回值為空的場景
某些Command類型的方法只執(zhí)行修改操作,不返回數(shù)據(jù),這個時候我們可以直接在Controller中返回void,Graceful Response會自動封裝默認的操作成功Response報文。
@Controller
public class Controller {
@RequestMapping("/void")
@ResponseBody
public void testVoidResponse() {
//省略業(yè)務(wù)操作
}
}
testVoidResponse方法的返回時void,調(diào)用這個接口時,將返回:
{
"status": {
"code": "200",
"msg": "success"
},
"payload": {}
}
3.4 Service方法業(yè)務(wù)處理
在引入Graceful Response后,Service層的方法的可讀性可以得到極大的提升。
?接口直接返回業(yè)務(wù)數(shù)據(jù)類型,而不是Response,更具備可讀性
public interface ExampleService {
UserInfoView query1(Query query);
}
?Service接口實現(xiàn)類中,直接拋自定義的業(yè)務(wù)異常,Graceful Response將其轉(zhuǎn)化為返回錯誤碼和錯誤提示
public class ExampleServiceImpl implements ExampleService {
@Resource
private UserInfoMapper mapper;
public UserInfoView query1(Query query) {
UserInfo userInfo = mapper.findOne(query.getId());
if (Objects.isNull(userInfo)) {
//這里直接拋自定義異常,異常通過@ExceptionMapper修飾,提供異常碼和異常提示
throw new NotFoundException();
}
// 省略后續(xù)業(yè)務(wù)操作
}
}
/**
* NotFoundException的定義,使用@ExceptionMapper注解修飾
* code:代表接口的異常碼
* msg:代表接口的異常提示
*/
@ExceptionMapper(code = "1404", msg = "找不到對象")
public class NotFoundException extends RuntimeException {
}
//Controller不再捕獲處理異常
@RequestMapping("/get")
@ResponseBody
public UserInfoView get(Query query)) {
return exampleService.query1(query);
}
當(dāng)Service方法拋出NotFoundException異常時,接口將直接返回錯誤碼,不需要手工set,極大地簡化了異常處理邏輯。
{
"status": {
"code": "1404",
"msg": "找不到對象"
},
"payload": {}
}
驗證:啟動example工程后,請求
http://localhost:9090/example/notfound
3.5 通用異常類和通用工具類
@ExceptionMapper
設(shè)計的初衷是將異常與錯誤碼關(guān)聯(lián)起來,用戶只需要拋異常,不需要再關(guān)注異常與錯誤碼的對應(yīng)關(guān)系。
部分用戶反饋,希望在不自定義新異常類的情況下,也能可以按照預(yù)期返回錯誤碼和異常信息,因此從2.1
版本開始,新增了GracefulResponseException
異常類,用戶只需要拋出該異常即可。
public class Service {
public void method() {
throw new GracefulResponseException("自定義的錯誤碼","自定義的錯誤信息");
}
}
為簡化使用,從2.1
版本開始提供GracefulResponse
通用工具類,在需要拋出GracefulResponseException
時,只需要調(diào)用raiseException
方法即可。 這樣設(shè)計原因是用戶拋通用異常,其實已經(jīng)不關(guān)心具體是什么異常了,用戶實際上只是想要錯誤碼和錯誤信息。
示例如下:
public class Service {
public void method() {
//當(dāng)condition==true時,拋出GracefulResponseException異常,返回自定義的錯誤碼和錯誤信息
if (condition) {
GracefulResponse.raiseException("自定義的錯誤碼", "自定義的錯誤信息");
}
//省略其他業(yè)務(wù)邏輯
}
}
3.6 參數(shù)校驗異常以及錯誤碼
在3.0
版本以前,如果引用validation框架并發(fā)生了校驗異常,Graceful Response在默認情況下會捕獲并返回code=1,參數(shù)校驗發(fā)生的異常信息會丟失;如果使用異常別名功能,可以對大的校驗異常返回統(tǒng)一的錯誤碼,但是不夠靈活且依舊沒有解決參數(shù)異常提示的問題。
Graceful Response從3.0版本開始,引入@ValidationStatusCode
注解,可以非常方便地支持validation校驗異常。
@ValidationStatusCode
注解目前只有一個code
屬性,用于指定參數(shù)校驗異常時的錯誤碼,錯誤提示則取自validation校驗框架。
對入?yún)㈩愡M行參數(shù)校驗
@Data
public class UserInfoQuery {
@NotNull(message = "userName is null !")
@Length(min = 6, max = 12)
@ValidationStatusCode(code = "520")
private String userName;
}
當(dāng)userName
字段任意一項校驗不通過時,接口將會返回異常碼520
和校驗注解中的message
:
{
"status": {
"code": "520",
"msg": "userName is null !"
},
"payload": {}
}
詳細見example工程ExampleController的validateDto方法http://localhost:9090/example/validateDto
注意:@ValidationStatusCode校驗參數(shù)對象字段的情況,code取值順序為:會先取字段上的注解,再去該屬性所在對象的類(即UserInfoQuery類)上的注解,再取全局配置的參數(shù)異常碼
gr.defaultValidateErrorCode
,最后取默認的全局默認的錯誤碼(默認code=1)
直接在Controller中校驗方法入?yún)?/p>
直接在Controller方法中進行參數(shù)校驗:
@Validated
public class ExampleController {
@RequestMapping("/validateMethodParam")
@ResponseBody
@ValidationStatusCode(code = "1314")
public void validateMethodParam(@NotNull(message = "userId不能為空") Long userId,
@NotNull(message = "userName不能為空") Long userName) {
//省略業(yè)務(wù)邏輯
}
}
當(dāng)userId、或者userName校驗不通過時,將會返回code=1314,msg為對應(yīng)的校驗信息。
{
"status": {
"code": "1314",
"msg": "userId不能為空"
},
"payload": {}
}
詳細見example工程ExampleController的validateMethodParam方法http://localhost:9090/example/validateMethodParam
注意:@ValidationStatusCode校驗Controller方法參數(shù)字段的情況,code取值順序為:會先取當(dāng)前方法上的注解,再去該方法所在類(即ExampleController類)上的注解,再取全局配置的參數(shù)異常碼
gr.defaultValidateErrorCode
,最后取默認的全局默認的錯誤碼(默認code=1)
4. 進階用法
4.1 Graceful Response異常錯誤碼處理
以下是使用Graceful Response進行異常、錯誤碼處理的開發(fā)步驟。
?創(chuàng)建自定義異常
通過繼承RuntimeException類創(chuàng)建自定義的異常,采用 @ExceptionMapper
注解修飾,注解的 code屬性為返回碼,msg屬性為錯誤提示信息。
關(guān)于是繼承RuntimeException還是繼承Exception,讀者可以根據(jù)實際情況去選擇,Graceful Response對兩者都支持。
@ExceptionMapper(code = "1007", msg = "有內(nèi)鬼,終止交易")
public static final class RatException extends RuntimeException {
}
?Service執(zhí)行具體邏輯
Service執(zhí)行業(yè)務(wù)邏輯的過程中,需要拋異常的時候直接拋出去即可。由于已經(jīng)通過@ExceptionMapper定義了該異常的錯誤碼,我們不需要再單獨的維護異常碼枚舉與異常類的關(guān)系。
//Service層偽代碼
public class Service {
public void illegalTransaction() {
//需要拋異常的時候直接拋
if (check()) {
throw new RatException();
}
doIllegalTransaction();
}
}
Controller層調(diào)用Service層偽代碼:
public class Controller {
@RequestMapping("/test3")
public void test3() {
//Controller中不會進行異常處理,也不會手工set錯誤碼,只關(guān)心核心操作,其他的統(tǒng)統(tǒng)交給Graceful Response
exampleService.illegalTransaction();
}
}
在瀏覽器中請求controller的/test3方法,有異常時將會返回:
{
"status": {
"code": "1007",
"msg": "有內(nèi)鬼,終止交易"
},
"payload": {
}
}
4.2 外部異常別名
案例工程( https://github.com/feiniaojin/graceful-response-example.git )啟動后, 通過瀏覽器訪問一個不存在的接口,例如 http://localhost:9090/example/get2?id=1
如果沒開啟Graceful Response,將會跳轉(zhuǎn)到404頁面,主要原因是應(yīng)用內(nèi)部產(chǎn)生了 NoHandlerFoundException異常。如果開啟了Graceful Response,默認會返回code=1的錯誤碼。
這類非自定義的異常,如果需要自定義一個錯誤碼返回,將不得不對每個異常編寫Advice邏輯,在Advice中設(shè)置錯誤碼和提示信息,這樣做也非常繁瑣。
Graceful Response可以非常輕松地解決給這類外部異常定義錯誤碼和提示信息的問題。
以下為操作步驟:
?創(chuàng)建異常別名,并用 @ExceptionAliasFor
注解修飾
@ExceptionAliasFor(code = "1404", msg = "Not Found", aliasFor = NoHandlerFoundException.class)
public class NotFoundException extends RuntimeException {
}
code:捕獲異常時返回的錯誤碼
msg:異常提示信息
aliasFor:表示將成為哪個異常的別名,通過這個屬性關(guān)聯(lián)到對應(yīng)異常。
?注冊異常別名
創(chuàng)建一個繼承了AbstractExceptionAliasRegisterConfig
的配置類,在實現(xiàn)的registerAlias方法中進行注冊。
@Configuration
public class GracefulResponseConfig extends AbstractExceptionAliasRegisterConfig {
@Override
protected void registerAlias(ExceptionAliasRegister aliasRegister) {
aliasRegister.doRegisterExceptionAlias(NotFoundException.class);
}
}
?瀏覽器訪問不存在的URL
再次訪問 http://localhost:9090/example/get2?id=1 ,服務(wù)端將返回以下json,正是在ExceptionAliasFor中定義的內(nèi)容
{
"code": "1404",
"msg": "not found",
"data": {
}
}
4.3 自定義Response格式
Graceful Response內(nèi)置了兩種風(fēng)格的響應(yīng)格式,可以在application.properties
文件中通過gr.responseStyle
進行配置。
?gr.responseStyle=0,或者不配置(默認情況)
將以以下的格式進行返回:
{
"status": {
"code": "1007",
"msg": "有內(nèi)鬼,終止交易"
},
"payload": {
}
}
?gr.responseStyle=1
將以以下的格式進行返回:
{
"code": "1404",
"msg": "not found",
"data": {
}
}
?自定義響應(yīng)格式
如果以上兩種格式均不能滿足業(yè)務(wù)需要,可以通過自定義去滿足,Response
例如以下響應(yīng):
public class CustomResponseImpl implements Response {
private String code;
private Long timestamp = System.currentTimeMillis();
private String msg;
private Object data = Collections.EMPTY_MAP;
@Override
public void setStatus(ResponseStatus statusLine) {
this.code = statusLine.getCode();
this.msg = statusLine.getMsg();
}
@Override
@JsonIgnore
public ResponseStatus getStatus() {
return null;
}
@Override
public void setPayload(Object payload) {
this.data = payload;
}
@Override
@JsonIgnore
public Object getPayload() {
return null;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Long getTimestamp() {
return timestamp;
}
}
注意,不需要返回的屬性可以返回null或者加上@JsonIgnore
注解
?配置gr.responseClassFullName
將CustomResponseImpl的全限定名配置到gr.responseClassFullName屬性。
gr.responseClassFullName=com.feiniaojin.gracefuresponse.example.config.CustomResponseImpl
注意,配置gr.responseClassFullName后,gr.responseStyle將不再生效。
實際的響應(yīng)報文如下:
{
"code":"200",
"timestamp":1682489591319,
"msg":"success",
"data":{
}
}
如果還是不能滿足需求,那么可以考慮同時自定義實現(xiàn)Response和ResponseFactory這兩個接口。
5. 常用配置
Graceful Response在版本迭代中,根據(jù)用戶反饋提供了一些常用的配置項,列舉如下:
gr.printExceptionInGlobalAdvice
是否打印異常日志,默認為false
gr.responseClassFullName
自定義Response類的全限定名,默認為空。 配置gr.responseClassFullName后,gr.responseStyle將不再生效
gr.responseStyle
Response風(fēng)格,不配置默認為0
gr.defaultSuccessCode
自定義的成功響應(yīng)碼,不配置則為0
gr.defaultSuccessMsg
自定義的成功提示,默認為ok
gr.defaultErrorCode
自定義的失敗響應(yīng)碼,默認為1
gr.defaultErrorMsg
自定義的失敗提示,默認為error
gr.defaultValidateErrorCode
全局的參數(shù)校驗錯誤碼,默認等于gr.defaultErrorCode
6. 總結(jié)
本文介紹了Graceful Response
這個框架的使用,讀者在使用過程中遇到問題,歡迎到GitHub提交issue進行反饋,幫助我們將Graceful Response優(yōu)化得更好。
審核編輯 黃宇
-
處理器
+關(guān)注
關(guān)注
68文章
19259瀏覽量
229649 -
接口
+關(guān)注
關(guān)注
33文章
8575瀏覽量
151014 -
封裝
+關(guān)注
關(guān)注
126文章
7873瀏覽量
142893
發(fā)布評論請先 登錄
相關(guān)推薦
評論