作者:京東物流?覃玉杰
1. 簡介
Graceful Response是一個(gè)Spring Boot體系下的優(yōu)雅響應(yīng)處理器,提供一站式統(tǒng)一返回值封裝、異常處理、異常錯(cuò)誤碼等功能。
使用Graceful Response進(jìn)行web接口開發(fā)不僅可以節(jié)省大量的時(shí)間,還可以提高代碼質(zhì)量,使代碼邏輯更清晰。
強(qiáng)烈推薦你花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)狀
我們進(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)、長度校驗(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對象中
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ò)誤碼功能,但可以看到代碼變得非常簡潔,可讀性非常強(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中央倉庫,可以直接引入到項(xiàng)目中,maven依賴如下:
<dependency>
<groupId>com.feiniaojin</groupId>
<artifactId>graceful-response</artifactId>
<version>2.0</version>
</dependency>
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í)例對象,調(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格式。
? 異常處理的場景
通過Graceful Response,我們不需要專門在Controller中處理異常,詳細(xì)見 4.1 Graceful Response異常錯(cuò)誤碼處理。
? 返回值為空的場景
某些Command類型的方法只執(zhí)行修改操作,不返回?cái)?shù)據(jù),這個(gè)時(shí)候我們可以直接在Controller中返回void,Graceful Response會自動(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 = "找不到對象")
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,極大地簡化了異常處理邏輯。
{
"status": {
"code": "1404",
"msg": "找不到對象"
},
"payload": {}
}
驗(yàn)證:啟動(dòng)example工程后,請求http://localhost:9090/example/notfound
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對兩者都支持。
@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中不會進(jìn)行異常處理,也不會手工set錯(cuò)誤碼,只關(guān)心核心操作,其他的統(tǒng)統(tǒng)交給Graceful Response
exampleService.illegalTransaction();
}
}
在瀏覽器中請求controller的/test3方法,有異常時(shí)將會返回:
{
"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,將會跳轉(zhuǎn)到404頁面,主要原因是應(yīng)用內(nèi)部產(chǎn)生了 NoHandlerFoundException
異常。如果開啟了Graceful Response,默認(rèn)會返回code=1的錯(cuò)誤碼。
這類非自定義的異常,如果需要自定義一個(gè)錯(cuò)誤碼返回,將不得不對每個(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)到對應(yīng)異常。
? 注冊異常別名
創(chuàng)建一個(gè)繼承了AbstractExceptionAliasRegisterConfig的配置類,在實(shí)現(xiàn)的registerAlias方法中進(jìn)行注冊。
@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.responseStyleResponse風(fēng)格,不配置默認(rèn)為0
? gr.defaultSuccessCode自定義的成功響應(yīng)碼,不配置則為0
? gr.defaultSuccessMsg自定義的成功提示,默認(rèn)為ok
? gr.defaultFailCode自定義的失敗響應(yīng)碼,默認(rèn)為1文章來源:http://www.zghlxwxcb.cn/news/detail-437130.html
? gr.defaultFailMsg自定義的失敗提示,默認(rèn)為error文章來源地址http://www.zghlxwxcb.cn/news/detail-437130.html
到了這里,關(guān)于一站式統(tǒng)一返回值封裝、異常處理、異常錯(cuò)誤碼解決方案—最強(qiáng)的Sping Boot接口優(yōu)雅響應(yīng)處理器的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!