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

