开发背景
现如今前后端分离已经是项目开发的主流方式,在前后端分离开发情形下,少不了前端和后端之间的友好交流,为了避免上升为物理交流,项目中必须要有一套规范有效的前后端协议格式。
后端开发的不同服务、不同业务处理并返回不同类型的数据,这不仅会增加巨大工作量来进行协议的输出,数据格式的多样化对于前端同事来讲也是一个灾难,这就需要对后端服务接口的返回格式定义成统一规范的结果类型。
前后端开发过程中数据交互规范化是一件非常重要的事情,不仅可以减少前后端交互过程中出现的问题,也让代码逻辑更加具有条理。
初始篇:从封装返回结果说起
返回结果类基本特征
对于后端的返回数据,考虑将格式统一后返回,在开发大量后端服务接口之后,根据开发经验可以总结得到,请求一个接口时需要关注的指标有:
- 响应状态码,即请求接口返回状态码,如 HTTP 请求中的 200、304、500 等状态
- 响应结果描述,有些接口请求成功或失败需要返回描述信息供前端展示
- 响应结果数据,大部分的接口都会返回后端获取的数据,并以列表的形式展示的前端页面中
- 是否成功:在实际项目中请求接口时,首先要关注的应该是接口的请求是否成功,然后才会去关注成功返回数据或者错误代码和信息,在统一数据中可以加入请求是否成功的标识,当然接口的成功与否也可以根据状态码可以判断,可以根据实际需求考虑是否定义结果状态
- 其他标识:为了显示更多接口调用的信息,可能会根据实际的业务需求加入接口调用的时间信息等。
除了以上属性特征外,返回结果类在定义时还应该满足:
松散的自定义返回结果
根据上述对返回结果基本特征的分析,我们可以定义一个如下代码所示为的返回结果类
public class Result {
private Integer code;
private String desc;
private Object data;
// 是否请求成功,本文使用 code = 10000 特指成功,其余为失败,因此不再冗余 success
// private Boolean success;
//请求时间,暂时不需要,可根据需求定义
//private long timestamp;
//构造器私有
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 等代码内容,引入了 lombok 工具,并使用注解 @Data 标注,代表当前类默认生成 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;
}
}
结果类使用方法
定义返回结果类后,Controller 对应的服务方法中就可以使用其作为返回结果类型,如下
@PostMapping("get")
public String getInfo(){
// 处理逻辑在这里
String result = "返回结构或";
// 封装返回结果
Result result = Result.ok();
result.setData(result);
return result;
}
进阶篇:枚举错误类和链式返回来加盟
实际面临的小问题
上述返回结果定义内容,尽管满足了基本需求,但是在使用时仍存在着如下的问题
为了解决上述问题,对现有的结果类进行优化处理,采用方法有
定义返回结果枚举类
首先定义返回结果枚举类,枚举类的使用可以进一步规范返回结果类中定义的属性取值。
@Getter
public enum ResultCodeEnum {
SUCCESS(20000,"响应成功"),
UNKNOWN_ERROR(20001,"未知错误"),
PARAM_ERROR(20002,"参数错误"),
NULL_POINT_ERROR(20003,"空指针异常"),
HTTP_CLIENT_ERROR(20003,"客户端连接异常");
/**
* 响应状态码
*/
private Integer code;
/**
* 响应描述信息
*/
private String desc;
ResultCodeEnum(Integer code, String desc){
this.code = code;
this.desc = desc;
}
}
@Getter 注解也是 lombok 提供的注解,代表为当前类属性仅生成 get 方法,枚举类不需要 set 方法,属性赋值通过定义枚举对象或者构造方法实现。
状态枚举以及链式返回实现
实现链式返回需要定义属性的 set 方法返回结果类型为当前结果类,并在方法中返回对象本身 this
。
@Data
public class Result {
private Integer code;
private String desc;
private Object data;
private Result() {}
/**
* 使用枚举类设置返回结果
* @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);
}
/**
* 返回结果类,使用链式编程
* 自定义成功标识
* @param
* @return
*/
public Result setSuccess(Boolen success){
this.setSuccess(success);
return this;
}
/**
* 返回结果类,使用链式编程
* 自定义状态码
* @param
* @return
*/
public Result setCode(Integer code){
this.setCode(code);
return this;
}
/**
* 返回结果类,使用链式编程
* 自定义返回结果描述
* @param
* @return
*/
public Result setDesc(String desc){
this.setDesc(desc);
return this;
}
/**
* 返回结果类,使用链式编程
* 自定义结果数据
* @param
* @return
*/
public Result setData(Object data){
this.setData(data);
return this;
}
}
lombok:我又来了
对于链式返回的处理 lombok 也提供了一个 @Accessors(chain = true)
代表为 set 方法实现链式返回结构,使用注解实现如下
@Data
@Accessors(chain = true)
public class Result {
private Integer code;
private String desc;
private Object data;
private Result() {}
/**
* 使用枚举类设置返回结果
* @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);
}
}
如上,整个返回结果类定义已经比较精简,通过 @Data 和 @Accessors(chain = true) 注解实现了get/set 方法和链式返回,并定义了通过枚举类创建对象的方法,并提供了直接返回的成功和失败方法。
结果类使用展示
@PostMapping("get")
public String getInfo(){
// 处理逻辑在这里
String result = "返回结构或";
// 封装返回结果,使用默认成功结果
// return Result.ok().setData(result);
// 封装返回结果,使用默认失败结果
// return Result.error();
// 封装返回结果,使用自定义枚举类
return Result.setResult(ResultCodeEnum.NULL_POINT_ERROR);
}
最终篇:建造者模式有话说
进阶之后的返回结果类已经很简洁,并且使用也比较方便,已经是一个完整的结果类了,可以满足大部分场景下的使用。
但是,对于代码开发来讲,就是要不断优化我们的代码结构,使之无论从看起来、还是用起来、以及讲起来都要更加的合理且优雅,那么这个时候,设计模式就有话说了。
在进阶篇中,我们使用了结果枚举 + 链式返回,已经有了建造者模式的影子了,结果枚举就类似于建造者对象的简陋版,链式返回在建造者对象属性赋值中也有使用。
接下来看一下使用建造者模式来实现返回结果类的方法
建造者和结果对象,相亲相爱一家人
标准的建造者模式认为,需要定义抽象接口来定义建造者的行为,并实现类来与目标对象关联。
为了方便及展示其密切关联性,我们实现一个简化版的建造者模式,并将建造者对象作为结果对象的内部静态类实现。
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;
}
// 定义静态方法创建 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();
}
}
}
使用建造者模式实现返回结果类,可以避免直接对返回结果类属性的修改,而是通过定义的建造者对象 builder 来赋值,保证了结果对象的数据安全。
内部静态建造者类使用
对于内部静态类创建时,需要携带其外部类名称才可以使用,如
Result result = new Result.ResultBuilder().data("result").build();
为了实际使用方便,可以在外部类中定义静态方法进行 builder 对象的创建,即 builder() 方法
// 使用时创建方法:Result.builder()
public static ResultBuilder builder(){
return new ResultBuilder();
}
此时创建方法可以写成
Result result = Result.builder().data("result").build();
是不是很熟悉!在许多优秀的框架使用过程中,重要对象的创建方式就是类似上述的建造者链式创建方式。
lombok: 继续上分
对于建造者模式的实现,lombok 也提供了实现方案,可以通过 @Builder
注解为类实现内部静态的建造者类,与上述代码基本一致,展现代码可以更简洁。
@Builder
public class Result {
private String code;
private String desc;
private Object data;
}
太简单了有木有!
@Builder 注解实现的建造者模式是最基本的形式,使用时需要注意
实际使用过程中,可以根据需要选择或定义适合的返回结果类
接口数据格式一览
定义好返回结果枚举类和最终的返回结果类后,在 controller 控制器中创建一个接口并返回统一结果类信息
@PostMapping("get")
public String getInfo(){
// 处理逻辑在这里
String result = "返回结果";
// 封装返回结果
return Result.builder().data(result).build();
}
通过 http 请求接口,可以得到如下格式的返回结果:
{
"code": 20000,
"desc": "查询成功",
"data": "返回结果";
}
这样,一个统一的结果返回类就创建成功了,在项目的开发过程中可以使用自定义的统一返回结果,如果使用了枚举类,只需要将返回结果枚举类维护起来,使用非常的方便哦。
最后
通过逐步的功能丰富,实现了一个满足基本使用需求的封装结果类,对项目开发过程会提供很大的帮助,提升编码效率并规范代码格式,并树立正确规范的代码观,希望每一位 coder 都能成长为参天大树,为行业添砖加瓦。
后续
结果类封装好了,但是却不想用怎么办?
相信很多人在开发中都有上述的想法,虽然你封装的很好,但是我就是不想用,这个时候我们就可以祭出 aop 神器,你不用我不用,成熟的代码自己动,感兴趣的小伙伴可以先学习起来哦。