Springboot系列之RestApi中获取枚举类属性的几种方式

本文主要是介绍Springboot系列之RestApi中获取枚举类属性的几种方式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

我们在日常开发中经常会遇到,实体类中的枚举属性,在通过@ResponseBody注解返回到前端后,默认被解析成了枚举对象的name值,当我们需要获取枚举类的详细属性时就会变得非常麻烦,今天给大家分享几种常用的获取枚举类属性的方式。

例子

我们先通过一个小例子讲解这个问题,首先我们要准备一个简单的接口,用于查询用户信息,用户实体中包含性别用户类型用户状态三种枚举属性,我们下面的例子中, 将会分别拿这三个枚举类进行测试。
代码如下:
用户实体User.java

@Data
public class User {private String username;private String site;private String[] git;private GenderEnum gender;private TypeEnum type;private StatusEnum status;
}

枚举类内容如下:
GenderEnum.java

public enum GenderEnum {MALE("男"),FAMALE("女");private String desc;GenderEnum(String desc) {this.desc = desc;}public String getDesc() {return desc;}
}

TypeEnum.java

public enum TypeEnum {ADMIN("管理员"),USER("普通用户");private String desc;TypeEnum(String desc) {this.desc = desc;}public String getDesc() {return desc;}
}

StatusEnum.java

public enum StatusEnum {INUSE("使用中"),UNUSED("未使用"),DISABLED("已禁用");private String desc;StatusEnum(String desc) {this.desc = desc;}public String getDesc() {return desc;}
}

Controller类RestApiController.java

@RestController
public class RestApiController {@RequestMapping("/users")public Object users() {List<User> userList = new ArrayList<>();User user = null;for (int i = 0; i < 2; i++) {user = new User();user.setGender(i % 2 == 0 ? GenderEnum.MALE : GenderEnum.FAMALE);user.setType(i % 2 == 0 ? TypeEnum.ADMIN : TypeEnum.USER);user.setStatus(i % 2 == 0 ? StatusEnum.INUSE : StatusEnum.DISABLED);user.setUsername("user_" + i);user.setSite("https://www.zhyd.me");user.setGit(new String[]{"https://gitee.com/yadong.zhang", "https://github.com/zhangyd-c"});userList.add(user);}return userList;}
}

接口返回数据的原始内容如下:

[{"username": "user_0","site": "https://www.zhyd.me","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"gender": "MALE","type": "ADMIN","status": "INUSE",},{"username": "user_1","site": "https://www.zhyd.me","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"gender": "FAMALE","type": "USER","status": "DISABLED"}
]

从接口数据中可以清楚的看到,枚举类属性被解析成了字符串。接下来我们将分别通过几种方式,完成对枚举类属性的获取。

第一种:新增Get方法

这种方式,需要我们在User.java实体类中,为用户类型枚举属性添加对应的get方法:

@Data
public class User {private String username;private String site;private String[] git;private GenderEnum gender;private TypeEnum type;private StatusEnum status;public String getTypeEnumDesc() {return null == type ? null : type.getDesc();}
}

接口响应内容:

[{"username": "user_0","site": "https://www.zhyd.me","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"gender": "MALE","type": "ADMIN","status": "INUSE","typeEnumDesc": "管理员"},{"username": "user_1","site": "https://www.zhyd.me","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"gender": "FAMALE","type": "USER","status": "DISABLED","typeEnumDesc": "普通用户"}
]

这种方式的优缺点:

  • 优点
    • 简单,不需要集成其他代码
  • 缺点
    • 扩展性差,如果一个枚举类存在多个属性,那我们就要在业务bean中为每个枚举属性单独增加get方法,想想都觉得头疼。

一般用来应急

第二种:通过自定义FastJsonHttpMessageConverter完成对Enum的转换

注:本人开发环境的fastjson版本为1.2.37, 另外测试了1.2.341.2.52,其他版本可能有所差异

改进枚举类

本小节只以TypeEnum.java枚举类为例,重写枚举类的toString方法

TypeEnu.java

public enum TypeEnum {ADMIN("管理员"),USER("普通用户");private String desc;TypeEnum(String desc) {this.desc = desc;}public String getDesc() {return desc;}@Overridepublic String toString() {return "{\\\"name\\\": \\\"" + this.name() + "\\\", \\\"desc\\\": \\\"" + this.getDesc() + "\\\"}";}
}

新增配置类

@Configuration
public class WebMvcConf implements WebMvcConfigurer {@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.forEach(System.out::println);}/*** 自定义FastJsonHttpMessageConverter处理api中的枚举*/@Configuration@ConditionalOnClass({FastJsonHttpMessageConverter.class})@ConditionalOnProperty(name = {"spring.http.converters.preferred-json-mapper"},havingValue = "fastjson",matchIfMissing = true)public class FastJsonHttpMessageConvertersConfiguration {@Bean@ConditionalOnMissingBean({FastJsonHttpMessageConverter.class})public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();FastJsonConfig fastJsonConfig = new FastJsonConfig();fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,SerializerFeature.WriteEnumUsingToString);converter.setFastJsonConfig(fastJsonConfig);return converter;}}
}

注:

  • @ConditionalOnClass表示当存在FastJsonHttpMessageConverter类时,才会加载该配置
  • @@ConditionalOnProperty表示当spring.http.converters.preferred-json-mapper配置项的值为fastjson时,才会加载该配置,并且即使没有(matchIfMissing)该property也会正常加载
  • @ConditionalOnMissingBean表示当没有注入FastJsonHttpMessageConverter类时,才会执行当前的注入操作

需要注意的是,一定要在SerializerFeature中添加SerializerFeature.WriteEnumUsingToString,表示在对枚举类序列化的时候,返回枚举类的toString()方法的值。关于SerializerFeature其他参数的说明,请参考https://blog.csdn.net/u010246789/article/details/52539576

重新访问接口,返回内容如下:

[{"gender":"MALE","status": "INUSE","git":["https:\/\/gitee.com\/yadong.zhang","https:\/\/github.com\/zhangyd-c"],"site":"https:\/\/www.zhyd.me","type":"{"name": "ADMIN", "desc": "绠$悊鍛�"}","typeEnumDesc":"绠$悊鍛�","username":"user_0"},{"gender":"FAMALE","status": "DISABLED","git":["https:\/\/gitee.com\/yadong.zhang","https:\/\/github.com\/zhangyd-c"],"site":"https:\/\/www.zhyd.me","type":"{"name": "USER", "desc": "鏅€氱敤鎴�"}","typeEnumDesc":"鏅€氱敤鎴�","username":"user_1"}
]

汉字全部乱码了,通过Debug发现,FastJsonHttpMessageConvertersupportedMediaTypes属性默认为*/*,这样就会造成浏览器在解析的时候无法识别具体的编码而导致解析失败,出现乱码问题。

我们修改一下fastJsonHttpMessageConverter方法,重新配置一下supportedMediaTypes,增加以下代码:

List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
converter.setSupportedMediaTypes(mediaTypes);

响应数据如下:

[{"gender": "MALE","status": "INUSE","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"site": "https://www.zhyd.me","type": "{\"name\": \"ADMIN\", \"desc\": \"管理员\"}","typeEnumDesc": "管理员","username": "user_0"},{"gender": "FAMALE","status": "DISABLED","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"site": "https://www.zhyd.me","type": "{\"name\": \"USER\", \"desc\": \"普通用户\"}","typeEnumDesc": "普通用户","username": "user_1"}
]

到这儿还有一个问题就是:接口返回的枚举属性,是一个字符串,前端使用时,并不能直接使用,需要通过JSON.parse(x)格式化成json串后才能使用,我们在后台处理一下,使接口直接返回map形式的内容。

改进

修改枚举类(TypeEnum)的toString方法,将枚举类的属性通过map包装成JSON

@Override
public String toString() {Map<String, Object> map = new HashMap<>();map.put("name", this.name());map.put("desc", this.getDesc());return JSONObject.toJSONString(map);
}

然后修改配置类的fastJsonHttpMessageConverter方法,通过ValueFilter处理序列化的参数

ValueFilter valueFilter = (o, s, o1) -> {if (o1 instanceof Enum) {String str = String.valueOf(o1);if (isJson(str)) {o1 = JSONObject.parseObject(String.valueOf(o1), Map.class);}}return o1;
};
fastJsonConfig.setSerializeFilters(valueFilter);

代码很好理解:当序列化参数为枚举类型时,将json字符串转为map(此处不是多余,因为SerializerFeature中针对枚举的序列化方式,只有两种(nameortoString),所以我们必须先将map转成字符串,然后再转成map)。
注:鉴于编码过程中可能会漏掉重写一些枚举类的toString方法(或者重写toString方法时并没有返回正确的json格式字符串),而枚举类默认的toString方法返回的并不是json形式的字符串,所以,需要使用isJson(str)判断当前枚举序列化的结果是否为json字符串。

WebMvcConf.java详细代码如下:

@Configuration
public class WebMvcConf implements WebMvcConfigurer {@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.forEach(System.out::println);}private boolean isJson(String str) {return !StringUtils.isEmpty(str) && (str.charAt(0) == '{' && str.charAt(str.length() - 1) == '}');}/*** 自定义FastJsonHttpMessageConverter处理api中的枚举*/@Configuration@ConditionalOnClass({FastJsonHttpMessageConverter.class})@ConditionalOnProperty(name = {"spring.http.converters.preferred-json-mapper"},havingValue = "fastjson",matchIfMissing = true)public class FastJsonHttpMessageConvertersConfiguration {@Bean@ConditionalOnMissingBean({FastJsonHttpMessageConverter.class})public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();FastJsonConfig fastJsonConfig = new FastJsonConfig();fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,SerializerFeature.WriteEnumUsingToString);List<MediaType> mediaTypes = new ArrayList<>();mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);converter.setSupportedMediaTypes(mediaTypes);ValueFilter valueFilter = (o, s, o1) -> {if (o1 instanceof Enum) {String str = String.valueOf(o1);if (isJson(str)) {o1 = JSONObject.parseObject(String.valueOf(o1), Map.class);}}return o1;};fastJsonConfig.setSerializeFilters(valueFilter);converter.setFastJsonConfig(fastJsonConfig);return converter;}}}

响应数据如下:

[{"gender": "MALE","status": "INUSE","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"site": "https://www.zhyd.me","type": {"name": "ADMIN","desc": "管理员"},"typeEnumDesc": "管理员","username": "user_0"},{"gender": "FAMALE","status": "DISABLED","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"site": "https://www.zhyd.me","type": {"name": "USER","desc": "普通用户"},"typeEnumDesc": "普通用户","username": "user_1"}
]

这种方式的优缺点:

  • 优点
    • 不需要对业务bean进行定制化,减少不必要的代码冗余和耦合
    • 扩展性好,枚举类中可以自定义多个属性
  • 缺点
    • 代码量大,新增了一个配置类
    • 每个枚举类中都需要OverridetoString方法,对代码的维护造成一定的影响

第三种:使用fastjson的@JSONType注解

因为Springboot默认使用的jackson的Converter,而@JSONType是fastjson的注解,所以这种方式也是基于FastJsonHttpMessageConverter的。

我们以StatusEnum.java为例,演示该注解的用法

首先修改StatusEnum.java为以下内容:

@JSONType(serializeEnumAsJavaBean = true)
public enum StatusEnum {INUSE("使用中"),UNUSED("未使用"),DISABLED("已禁用");private String desc;StatusEnum(String desc) {this.desc = desc;}public String getDesc() {return desc;}public String getName() {return this.name();}
}

注:如果此处存在反序列化异常,请参考:https://github.com/alibaba/fastjson/issues/1452

响应结果:

[{"gender": "MALE","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"site": "https://www.zhyd.me","status": {"desc": "使用中","name": "INUSE"},"type": {"name": "ADMIN","desc": "管理员"},"typeEnumDesc": "管理员","username": "user_0"},{"gender": "FAMALE","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"site": "https://www.zhyd.me","status": {"desc": "已禁用","name": "DISABLED"},"type": {"name": "USER","desc": "普通用户"},"typeEnumDesc": "普通用户","username": "user_1"}
]

效果和第二步的一致。

这种方式的优缺点:

  • 优点
    • 不需要对业务bean进行定制化,减少不必要的代码冗余和耦合
    • 扩展性好,枚举类中可以自定义多个属性
    • 不用去定制toString方法
    • 使用注解配置枚举类,方便快捷
  • 缺点
    • 依赖FastJsonHttpMessageConverter,需要单独对HttpMessageConverter进行增强

第四种:使用jackson的@JsonFormat注解

这一步开始,我们先把第二种方案中添加的FastJsonHttpMessageConvertersConfiguration类注掉,这样能更清楚的对比效果。

我们修改GenderEnum类内容如下:

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum GenderEnum {MALE("男"),FAMALE("女");private String desc;GenderEnum(String desc) {this.desc = desc;}public String getDesc() {return desc;}
}

访问接口返回的数据结构如下:

[{"username": "user_0","site": "https://www.zhyd.me","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"gender": {"desc": "男"},"type": "ADMIN","status": "INUSE","typeEnumDesc": "管理员"},{"username": "user_1","site": "https://www.zhyd.me","git": ["https://gitee.com/yadong.zhang","https://github.com/zhangyd-c"],"gender": {"desc": "女"},"type": "USER","status": "DISABLED","typeEnumDesc": "普通用户"}
]

已经成功序列化了枚举,因为我们枚举类中只有一个getDesc方法,所以序列化出来的枚举内容,也只有这一个参数。当实际生产环境中的枚举类存在多个属性时,只需要分别加上getXxx方法即可。

这种方式的优缺点:

  • 优点
    • 不需要对业务bean进行定制化,减少不必要的代码冗余和耦合
    • 扩展性好,枚举类中可以自定义多个属性
    • 不用去定制toString方法
    • 使用注解配置枚举类,方便快捷
    • Jackson自带注解,不依赖自定义的HttpMessageConverter,减少代码量
  • 缺点
    • 暂无

测试中碰到的问题

作者在写测试用例的时候,因不小心,碰到过几个问题,在这儿记录一下,防止后面有朋友继续碰到。

  1. JSONType是fastjson的注解,JsonFormat是jackson的注解,在使用自定义的FastJsonHttpMessageConverter组件时,JsonFormat是不可用的,同理在禁用掉FastJsonHttpMessageConverter组件后,JSONType不可用
  2. 其他问题,多见于参考资料的链接

项目源码

https://github.com/zhangyd-c/springboot-learning/tree/master/springboot-enums

参考资料

  • https://www.jianshu.com/p/68a75c093023
  • https://blog.csdn.net/gottst0113/article/details/80978966
  • https://blog.csdn.net/u010246789/article/details/52539576
  • https://blog.csdn.net/mickjoust/article/details/51671060
  • https://blog.csdn.net/qq_41599820/article/details/86607029
  • https://blog.csdn.net/zhangfengaiCQ/article/details/82657926
  • https://github.com/alibaba/fastjson/issues/1452

相关文章

  • springboot系列(一)整合Freemark模板(详尽版)
  • springboot系列(二)整合Mybatis+Mapper+Pagehelper(修订-详尽版)
  • springboot系列(三)一文带你搞懂Scheduler定时器(详尽版)
  • springboot系列(四)实现Freemarker自定义标签

推荐文章:

  • 给大家分享一个不可错过的、超棒的学习资源!错过后悔【非广告】
  • 【安利】这应该是史上最全的整合第三方登录的开源库
  • blog-hunter,一款简单好用并且支持多个平台的博客爬取工具

其他开源作品

  • blog-hunter,一款简单好用并且支持多个平台的博客爬取工具
  • OneBlog,一个简洁美观、功能强大并且自适应的Java博客
  • JustAuth,史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。Login, so easy!

关注我的公众号

如果本文帮助到了您,麻烦您关注一下本公号。更多原创技术文章、开源作品更新记录将会及时通知到您。
感谢生命中遇到你。

这篇关于Springboot系列之RestApi中获取枚举类属性的几种方式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1020131

相关文章

虚拟机与物理机的文件共享方式

《虚拟机与物理机的文件共享方式》文章介绍了如何在KaliLinux虚拟机中实现物理机文件夹的直接挂载,以便在虚拟机中方便地读取和使用物理机上的文件,通过设置和配置,可以实现临时挂载和永久挂载,并提供... 目录虚拟机与物理机的文件共享1 虚拟机设置2 验证Kali下分享文件夹功能是否启用3 创建挂载目录4

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

Git中恢复已删除分支的几种方法

《Git中恢复已删除分支的几种方法》:本文主要介绍在Git中恢复已删除分支的几种方法,包括查找提交记录、恢复分支、推送恢复的分支等步骤,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录1. 恢复本地删除的分支场景方法2. 恢复远程删除的分支场景方法3. 恢复未推送的本地删除分支场景方法4. 恢复

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

Java访问修饰符public、private、protected及默认访问权限详解

《Java访问修饰符public、private、protected及默认访问权限详解》:本文主要介绍Java访问修饰符public、private、protected及默认访问权限的相关资料,每... 目录前言1. public 访问修饰符特点:示例:适用场景:2. private 访问修饰符特点:示例:

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

在C#中获取端口号与系统信息的高效实践

《在C#中获取端口号与系统信息的高效实践》在现代软件开发中,尤其是系统管理、运维、监控和性能优化等场景中,了解计算机硬件和网络的状态至关重要,C#作为一种广泛应用的编程语言,提供了丰富的API来帮助开... 目录引言1. 获取端口号信息1.1 获取活动的 TCP 和 UDP 连接说明:应用场景:2. 获取硬