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