淺析SpringBoot統(tǒng)一返回結(jié)果的實現(xiàn)
開發(fā)背景
現(xiàn)如今前后端分離已經(jīng)是項目開發(fā)的主流方式,在前后端分離開發(fā)情形下,少不了前端和后端之間的友好交流,為了避免上升為物理交流,項目中必須要有一套規(guī)范有效的前后端協(xié)議格式。
后端開發(fā)的不同服務(wù)、不同業(yè)務(wù)處理并返回不同類型的數(shù)據(jù),這不僅會增加巨大工作量來進行協(xié)議的輸出,數(shù)據(jù)格式的多樣化對于前端同事來講也是一個災(zāi)難,這就需要對后端服務(wù)接口的返回格式定義成統(tǒng)一規(guī)范的結(jié)果類型。
前后端開發(fā)過程中數(shù)據(jù)交互規(guī)范化是一件非常重要的事情,不僅可以減少前后端交互過程中出現(xiàn)的問題,也讓代碼邏輯更加具有條理。
初始篇:從封裝返回結(jié)果說起
返回結(jié)果類基本特征
對于后端的返回數(shù)據(jù),考慮將格式統(tǒng)一后返回,在開發(fā)大量后端服務(wù)接口之后,根據(jù)開發(fā)經(jīng)驗可以總結(jié)得到,請求一個接口時需要關(guān)注的指標(biāo)有:
- 響應(yīng)狀態(tài)碼,即請求接口返回狀態(tài)碼,如 HTTP 請求中的 200、304、500 等狀態(tài)
- 響應(yīng)結(jié)果描述,有些接口請求成功或失敗需要返回描述信息供前端展示
- 響應(yīng)結(jié)果數(shù)據(jù),大部分的接口都會返回后端獲取的數(shù)據(jù),并以列表的形式展示的前端頁面中
- 是否成功:在實際項目中請求接口時,首先要關(guān)注的應(yīng)該是接口的請求是否成功,然后才會去關(guān)注成功返回數(shù)據(jù)或者錯誤代碼和信息,在統(tǒng)一數(shù)據(jù)中可以加入請求是否成功的標(biāo)識,當(dāng)然接口的成功與否也可以根據(jù)狀態(tài)碼可以判斷,可以根據(jù)實際需求考慮是否定義結(jié)果狀態(tài)
- 其他標(biāo)識:為了顯示更多接口調(diào)用的信息,可能會根據(jù)實際的業(yè)務(wù)需求加入接口調(diào)用的時間信息等。
除了以上屬性特征外,返回結(jié)果類在定義時還應(yīng)該滿足:
- 屬性私有化,使用 get/set 方法來操作屬性值
- 構(gòu)造器私有化,外部只可以調(diào)用方法,初始化要在類內(nèi)部完成
- 由于外部需要直接調(diào)用方法,因此方法要定義為靜態(tài)方法
松散的自定義返回結(jié)果
根據(jù)上述對返回結(jié)果基本特征的分析,我們可以定義一個如下代碼所示為的返回結(jié)果類
public class Result { private Integer code; private String desc; private Object data; // 是否請求成功,本文使用 code = 10000 特指成功,其余為失敗,因此不再冗余 success // private Boolean success; //請求時間,暫時不需要,可根據(jù)需求定義 //private long timestamp; //構(gòu)造器私有 private Result() {} //get/set 方法 public Boolean getSuccess() { return success; } public void setSuccess(Boolean success) { this.success = success; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } /** * 返回通用成功 * @return Result */ public static Result ok(){ Result result = new Result(); result.setSuccess(true); result.setCode("20000"); result.setDesc("請求成功"); return result; } /** * 返回通用失敗,未知錯誤 * @return Result */ public static Result error(){ Result result = new Result(); result.setSuccess(false); result.setCode(20001); result.setDesc("請求失敗"); return result; } }
lombok:代碼簡潔利器
為了減少 get/set 等代碼內(nèi)容,引入了 lombok 工具,并使用注解 @Data 標(biāo)注,代表當(dāng)前類默認(rèn)生成 set/get 方法
@Data public class Result { private Integer code; private String desc; private Object data; private Result() {} /** * 返回通用成功 * @return Result */ public static Result ok(){ Result result = new Result(); result.setCode("20000"); result.setDesc("請求成功"); return result; } /** * 返回通用失敗,未知錯誤 * @return Result */ public static Result error(){ Result result = new Result(); result.setCode(20001); result.setDesc("請求失敗"); return result; } }
結(jié)果類使用方法
定義返回結(jié)果類后,Controller 對應(yīng)的服務(wù)方法中就可以使用其作為返回結(jié)果類型,如下
@PostMapping("get") public String getInfo(){ // 處理邏輯在這里 String result = "返回結(jié)構(gòu)或"; // 封裝返回結(jié)果 Result result = Result.ok(); result.setData(result); return result; }
進階篇:枚舉錯誤類和鏈?zhǔn)椒祷貋砑用?/h2>
實際面臨的小問題
上述返回結(jié)果定義內(nèi)容,盡管滿足了基本需求,但是在使用時仍存在著如下的問題
- 返回結(jié)果需要先初始化,然后再進行結(jié)果賦值處理,相當(dāng)于返回值仍需要手動添加,這樣即增加了數(shù)據(jù)錯誤的風(fēng)險,并且并沒有減少實際代碼量,不能凸顯統(tǒng)一封裝帶來的好處。
- 上述封裝類中,分別定義了返回成功和失敗的靜態(tài)方法,但是對于失敗的結(jié)果可能是多樣的,不可能針對每種失敗分別定義對應(yīng)的靜態(tài)方法,這樣即繁瑣又不現(xiàn)實。
為了解決上述問題,對現(xiàn)有的結(jié)果類進行優(yōu)化處理,采用方法有
- 使用返回對象本身的方式來簡化對象初始化和賦值步驟,簡潔代碼,突出重點
- 采用返回結(jié)果枚舉類的方式將所有可能返回的結(jié)果定義為枚舉類常量,在返回結(jié)果類中使用對應(yīng)的枚舉類返回創(chuàng)建,以此處理異常結(jié)果多樣性問題
定義返回結(jié)果枚舉類
首先定義返回結(jié)果枚舉類,枚舉類的使用可以進一步規(guī)范返回結(jié)果類中定義的屬性取值。
@Getter public enum ResultCodeEnum { SUCCESS(20000,"響應(yīng)成功"), UNKNOWN_ERROR(20001,"未知錯誤"), PARAM_ERROR(20002,"參數(shù)錯誤"), NULL_POINT_ERROR(20003,"空指針異常"), HTTP_CLIENT_ERROR(20003,"客戶端連接異常"); /** * 響應(yīng)狀態(tài)碼 */ private Integer code; /** * 響應(yīng)描述信息 */ private String desc; ResultCodeEnum(Integer code, String desc){ this.code = code; this.desc = desc; } }
@Getter 注解也是 lombok 提供的注解,代表為當(dāng)前類屬性僅生成 get 方法,枚舉類不需要 set 方法,屬性賦值通過定義枚舉對象或者構(gòu)造方法實現(xiàn)。
狀態(tài)枚舉以及鏈?zhǔn)椒祷貙崿F(xiàn)
實現(xiàn)鏈?zhǔn)椒祷匦枰x屬性的 set 方法返回結(jié)果類型為當(dāng)前結(jié)果類,并在方法中返回對象本身 this
。
@Data public class Result { private Integer code; private String desc; private Object data; private Result() {} /** * 使用枚舉類設(shè)置返回結(jié)果 * @param resultCodeEnum * @return */ public static Result setResult(ResultCodeEnum resultCodeEnum){ Result result = new Result(); result.setCode(resultCodeEnum.getCode()); result.setDesc(resultCodeEnum.getDesc()); return result; } /** * 返回通用成功 * @return Result */ public static Result ok(){ // 鏈?zhǔn)教幚? return new Result().setResult(ResultCodeEnum.SUCCESS); } /** * 返回通用失敗,未知錯誤 * @return Result */ public static Result error(){ // 鏈?zhǔn)教幚? return new Result().setResult(ResultCodeEnum.UNKNOWN_ERROR); } /** * 返回結(jié)果類,使用鏈?zhǔn)骄幊? * 自定義成功標(biāo)識 * @param * @return */ public Result setSuccess(Boolen success){ this.setSuccess(success); return this; } /** * 返回結(jié)果類,使用鏈?zhǔn)骄幊? * 自定義狀態(tài)碼 * @param * @return */ public Result setCode(Integer code){ this.setCode(code); return this; } /** * 返回結(jié)果類,使用鏈?zhǔn)骄幊? * 自定義返回結(jié)果描述 * @param * @return */ public Result setDesc(String desc){ this.setDesc(desc); return this; } /** * 返回結(jié)果類,使用鏈?zhǔn)骄幊? * 自定義結(jié)果數(shù)據(jù) * @param * @return */ public Result setData(Object data){ this.setData(data); return this; } }
lombok:我又來了
對于鏈?zhǔn)椒祷氐奶幚?lombok 也提供了一個 @Accessors(chain = true)
代表為 set 方法實現(xiàn)鏈?zhǔn)椒祷亟Y(jié)構(gòu),使用注解實現(xiàn)如下
@Data @Accessors(chain = true) public class Result { private Integer code; private String desc; private Object data; private Result() {} /** * 使用枚舉類設(shè)置返回結(jié)果 * @param resultCodeEnum * @return */ public static Result setResult(ResultCodeEnum resultCodeEnum){ Result result = new Result(); result.setCode(resultCodeEnum.getCode()); result.setDesc(resultCodeEnum.getDesc()); return result; } /** * 返回通用成功 * @return Result */ public static Result ok(){ return new Result().setResult(ResultCodeEnum.SUCCESS); } /** * 返回通用失敗,未知錯誤 * @return Result */ public static Result error(){ return new Result().setResult(ResultCodeEnum.UNKNOWN_ERROR); } }
如上,整個返回結(jié)果類定義已經(jīng)比較精簡,通過 @Data 和 @Accessors(chain = true) 注解實現(xiàn)了get/set 方法和鏈?zhǔn)椒祷兀⒍x了通過枚舉類創(chuàng)建對象的方法,并提供了直接返回的成功和失敗方法。
結(jié)果類使用展示
@PostMapping("get") public String getInfo(){ // 處理邏輯在這里 String result = "返回結(jié)構(gòu)或"; // 封裝返回結(jié)果,使用默認(rèn)成功結(jié)果 // return Result.ok().setData(result); // 封裝返回結(jié)果,使用默認(rèn)失敗結(jié)果 // return Result.error(); // 封裝返回結(jié)果,使用自定義枚舉類 return Result.setResult(ResultCodeEnum.NULL_POINT_ERROR); }
最終篇:建造者模式有話說
進階之后的返回結(jié)果類已經(jīng)很簡潔,并且使用也比較方便,已經(jīng)是一個完整的結(jié)果類了,可以滿足大部分場景下的使用。
但是,對于代碼開發(fā)來講,就是要不斷優(yōu)化我們的代碼結(jié)構(gòu),使之無論從看起來、還是用起來、以及講起來都要更加的合理且優(yōu)雅,那么這個時候,設(shè)計模式就有話說了。
在進階篇中,我們使用了結(jié)果枚舉 + 鏈?zhǔn)椒祷?,已?jīng)有了建造者模式的影子了,結(jié)果枚舉就類似于建造者對象的簡陋版,鏈?zhǔn)椒祷卦诮ㄔ煺邔ο髮傩再x值中也有使用。
接下來看一下使用建造者模式來實現(xiàn)返回結(jié)果類的方法
建造者和結(jié)果對象,相親相愛一家人
標(biāo)準(zhǔn)的建造者模式認(rèn)為,需要定義抽象接口來定義建造者的行為,并實現(xiàn)類來與目標(biāo)對象關(guān)聯(lián)。
為了方便及展示其密切關(guān)聯(lián)性,我們實現(xiàn)一個簡化版的建造者模式,并將建造者對象作為結(jié)果對象的內(nèi)部靜態(tài)類實現(xiàn)。
public class Result { private String code; private String desc; private Object data; private Result(ResultBuilder resultBuilder) { this.code = resultBuilder.code; this.desc = resultBuilder.desc; this.data = resultBuilder.data; } // 定義靜態(tài)方法創(chuàng)建 ResultBuilder 類,否則使用時需要 new Result.ResultBuilder() public static ResultBuilder builder(){ return new ResultBuilder(); } public static class ResultBuilder{ private String code; private String desc; private T data; public ResultBuilder code(String code) { this.code = code; return this; } public ResultBuilder desc(String desc) { this.desc = desc; return this; } public ResultBuilder data(Object data) { this.data = data; return this; } public ResultBuilder resultCodeEnum(ResultCodeEnum resultCodeEnum){ this.success = resultCodeEnum.getSuccess(); this.code = resultCodeEnum.getCode(); this.desc = resultCodeEnum.getDesc(); return this; } public Result build(){ Objects.requireNonNull(this.success); return new Result(this); } public Result successBuild(){ return this.resultCodeEnum(ResultCodeEnum.SUCCESS).build(); } public Result errorBuild(){ return this.resultCodeEnum(ResultCodeEnum.UNKNOWN_ERROR).build(); } } }
使用建造者模式實現(xiàn)返回結(jié)果類,可以避免直接對返回結(jié)果類屬性的修改,而是通過定義的建造者對象 builder 來賦值,保證了結(jié)果對象的數(shù)據(jù)安全。
內(nèi)部靜態(tài)建造者類使用
對于內(nèi)部靜態(tài)類創(chuàng)建時,需要攜帶其外部類名稱才可以使用,如
Result result = new Result.ResultBuilder().data("result").build();
為了實際使用方便,可以在外部類中定義靜態(tài)方法進行 builder 對象的創(chuàng)建,即 builder() 方法
// 使用時創(chuàng)建方法:Result.builder() public static ResultBuilder builder(){ return new ResultBuilder(); }
此時創(chuàng)建方法可以寫成
Result result = Result.builder().data("result").build();
是不是很熟悉!在許多優(yōu)秀的框架使用過程中,重要對象的創(chuàng)建方式就是類似上述的建造者鏈?zhǔn)絼?chuàng)建方式。
lombok: 繼續(xù)上分
對于建造者模式的實現(xiàn),lombok 也提供了實現(xiàn)方案,可以通過 @Builder
注解為類實現(xiàn)內(nèi)部靜態(tài)的建造者類,與上述代碼基本一致,展現(xiàn)代碼可以更簡潔。
@Builder public class Result { private String code; private String desc; private Object data; }
太簡單了有木有!
@Builder 注解實現(xiàn)的建造者模式是最基本的形式,使用時需要注意
- @Builder 注解只會為 Result 類定義全參數(shù)構(gòu)造方法供 buidler 使用,沒有無參構(gòu)造,如果需要要自己實現(xiàn)或使用 @AllArgsConstructor 和 @NoArgsConstructor 注解
- 上述代碼中沒有使用 @Data 注解,Result 對象的屬性不可修改,可以通過屬性名稱獲取,如需要可以自行添加
- @Builder 注解實現(xiàn)的建造者模式雖然簡單,但是太簡單,無法使用我們進階篇提到的枚舉結(jié)果來實現(xiàn)返回對象,因此需要手動實現(xiàn)對應(yīng)創(chuàng)建方法
實際使用過程中,可以根據(jù)需要選擇或定義適合的返回結(jié)果類
接口數(shù)據(jù)格式一覽
定義好返回結(jié)果枚舉類和最終的返回結(jié)果類后,在 controller 控制器中創(chuàng)建一個接口并返回統(tǒng)一結(jié)果類信息
@PostMapping("get") public String getInfo(){ // 處理邏輯在這里 String result = "返回結(jié)果"; // 封裝返回結(jié)果 return Result.builder().data(result).build(); }
通過 http 請求接口,可以得到如下格式的返回結(jié)果:
{ "code": 20000, "desc": "查詢成功", "data": "返回結(jié)果"; }
這樣,一個統(tǒng)一的結(jié)果返回類就創(chuàng)建成功了,在項目的開發(fā)過程中可以使用自定義的統(tǒng)一返回結(jié)果,如果使用了枚舉類,只需要將返回結(jié)果枚舉類維護起來,使用非常的方便哦。
最后
通過逐步的功能豐富,實現(xiàn)了一個滿足基本使用需求的封裝結(jié)果類,對項目開發(fā)過程會提供很大的幫助,提升編碼效率并規(guī)范代碼格式,并樹立正確規(guī)范的代碼觀,希望每一位 coder 都能成長為參天大樹,為行業(yè)添磚加瓦。
到此這篇關(guān)于淺析SpringBoot統(tǒng)一返回結(jié)果的實現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot統(tǒng)一返回結(jié)果內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot中JSONObject遍歷并替換部分json值
這篇文章主要介紹了springboot中JSONObject遍歷并替換部分json值,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11VsCode配置java環(huán)境的詳細(xì)圖文教程
vscode是一個免費的代碼編輯器,支持多種主題,應(yīng)用起來簡單方便,下面這篇文章主要給大家介紹了關(guān)于VsCode配置java環(huán)境的詳細(xì)圖文教程,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02基于Beanutils.copyProperties()的用法及重寫提高效率
這篇文章主要介紹了Beanutils.copyProperties( )的用法及重寫提高效率的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09SpringBoot集成ElasticSearch的示例代碼
Elasticsearch是用Java語言開發(fā)的,并作為Apache許可條款下的開放源碼發(fā)布,是一種流行的企業(yè)級搜索引擎,本文給大家介紹SpringBoot集成ElasticSearch的示例代碼,感興趣的朋友一起看看吧2022-02-02Java Swing實現(xiàn)坦克大戰(zhàn)游戲
這篇文章主要介紹了Java Swing實現(xiàn)坦克大戰(zhàn)游戲,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有很大的幫助喲,需要的朋友可以參考下2021-05-05詳解使用Spring的restTemplete進行Http請求
本篇文章主要介紹了詳解使用Spring的restTemplete進行Http請求,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06利用SpringBoot和LiteFlow解鎖復(fù)雜流程
隨著業(yè)務(wù)的復(fù)雜化,企業(yè)需要更加高效、便捷地管理自己的業(yè)務(wù)流程,這就需要借助一些流程引擎實現(xiàn),今天,我們就來介紹一種基于Java語言開發(fā)的輕量級工作流引擎——LiteFlow,以及如何在Spring Boot框架中集成它,從而提高企業(yè)的工作效率和開發(fā)效率2023-06-06