枚举虽好,但务必记得避坑

网站建设3年前发布
27 0 0

枚举作为 Java 5 的重要特征,相信大家并不陌生,但在实际开发过程中,当 name 和 ordrial 发生变化时,如果处理不当非常容易引起系统bug。这种兼容性bug非常难以定位,需要从框架层次进行避免,而非仅靠开发人员的主观意识。,枚举很好用,特别是提供的 name 和 ordrial 特性,但这点对重构造成了一定影响,比如:,某个枚举值业务语义发生变化,需要将其进行 rename 操作,以更好的表达新业务语义,新增、删除或者为了展示调整了枚举定义顺序,这些在业务开发中非常常见,使用 IDE 的 refactor 功能可以快速且准确的完成重构工作。但,如果系统将这些暴露出去或者存储到数据库等存储引擎就变得非常麻烦,不管是 name 还是 ordrial 的变更都会产生兼容性问题。,对此,最常见的解决方案便是放弃使用 name 和 ordrial,转而使用控制能力更强的 code。,提供一组工具,以方便的基于 code 使用枚举,快速完成对现有框架的集成:,完成与 Spring MVC 的集成,基于 code 使用枚举;加强返回值,以对象的方式进行返回,信息包括 code、name、description,提供统一的枚举字典,自动扫描系统中的枚举并将其以 restful 的方式暴露给前端,使用 code 进行数据存储操作,避免重构的影响,在 Spring boot 项目的 pom 中增加如下依赖:,如何统一枚举行为呢?公共父类肯定是不行的,但可以为其提供一个接口,在接口中完成行为的定义。,2.2.1. 定义枚举接口,除了在枚举中自定义 code 外,通常还会为其提供描述信息,构建接口如下:,整体结构如下:,20230306154533537647150b8bb0e2325018d07a835fda3323eb982,在定义枚举时便可以直接使用CommonEnum这个接口。,2.2.2. 实现枚举接口,有了统一的枚举接口,在定义枚举时便可以直接实现接口,从而完成对枚举的约束。,有了统一的 CommonEnum 最大的好处便是可以进行统一管理,对于统一管理,第一件事便是找到并注册所有的 CommonEnum。,2023030701454765b020787cf868eace4280d7f4961872fe6653883,以上是核心处理流程:,将 CommonEnum 实现类注册到两个 Map 中进行缓存,备注:此处万万不可直接使用反射技术,反射会触发类的自动加载,将对众多不需要的类进行加载,从而增加 metaspace 的压力。,在需要 CommonEnum 时,只需注入 CommonEnumRegistry Bean 便可以方便的获得 CommonEnum 的具体实现。,Web 层是最常见的接入点,对于 CommonEnum 我们倾向于:,参数使用 code 来表示,避免 name、ordrial 变化导致业务异常,丰富返回值,包括枚举的 code、name、description 等,20230307014548386c69303c3c1458cd3221ee7faeb529875326581,2.4.1. 入参转化,Spring MVC 存在两种参数转化扩展:,根据目标类型获取所有的 枚举值,并根据 code 和 name 进行转化,对于 Json 参数,需要对 Json 框架进行扩展(以 Jackson 为例),遍历 CommonEnumRegistry 提供的所有 CommonEnum,依次进行注册,从 Json 中读取信息,根据 code 和 name 转化为确定的枚举值,两种扩展核心实现见:,2.4.2. 增强返回值,默认情况下,对于枚举类型在转换为 Json 时,只会输出 name,其他信息会出现丢失,对于展示非常不友好,对此,需要对 Json 序列化进行能力增强。,首先,需要定义 CommonEnum 对应的返回对象,具体如下:,CommonEnumVO 是一个标准的 POJO,只是增加了 Swagger 相关注解。,CommonEnumJsonSerializer 是自定义序列化的核心,会将 CommonEnum 封装为 CommonEnumVO 并进行写回,具体如下:,2.4.3. 效果展示,首先,新建一个测试枚举 NewsStatus,具体如下:,然后新建 EnumController,具体如下:,执行结果如下:,20230306153916a6bcd8c870e62718d5c772c676dd9ff6e4a57f346,整体符合预期:,使用 code 作为请求参数可以自动转化为对应的 CommonEnum,使用 CommonEnum 作为返回值,返回标准的 CommonEnumVO 对象结构,有时可以将 枚举 理解为系统的一类字段,比较典型的就是管理页面的各种下拉框,下拉框中的数据来自于后台服务。,有了 CommonEnum 之后,可以提供统一的一组枚举字典,避免重复开发,同时在新增枚举时也无需进行扩展,系统自动识别并添加到字典中。,2.5.1. 构建字典Controller,在 CommonEnumRegistry 基础之上实现通用字典接口非常简单,只需按规范构建 Controller 即可,具体如下:,该 Controller 提供如下能力:,获取全部字典,一次性获取系统中所有的 CommonEnum,获取所有字典类型,仅获取字典类型,通常用于测试,获取指定字典类型的全部信息,比如上述所说的填充下拉框,2.5.2. 效果展示,获取全部字典:,20230306153917a309c1113b93c44c6566765d9db25ec28e22e0496,获取所有字典类型:,20230306153917e489c244741aab0f9be082a0a7b0fd9adbec32195,获取指定字段类型的全部信息:,202303061539188637f1381161af98b9d5797b8ec99223714dd9145,输出适配器主要以 ORM 框架为主,同时各类 ORM 框架均提供了类型映射的扩展点,通过该扩展点可以对 CommonEnum 使用 code 进行存储。,2.6.1. MyBatis 支持,MyBatis 作为最流行的 ORM 框架,提供了 TypeHandler 用于处理自定义的类型扩展。,MyBatisNewsStatusHandler 通过 @MappedTypes(NewsStatus.class) 对其进行标记,以告知框架该 Handler 是用于 NewsStatus 类型的转换。,CommonEnumTypeHandler 是为 CommonEnum 提供的通用转化能力,具体如下:,由于逻辑比较简单,在此不做过多解释。,有了类型之后,需要在 spring boot 的配置文件中指定 type-handler 的加载逻辑,具体如下:,完成配置后,使用 Mapper 对数据进行持久化,数据表中存储的便是 code 信息,具体如下:,20230306153918632a45809365c82d21b362729ab7e0e81ca555140,2.6.2. JPA 支持,随着 Spring data 越来越流行,JPA 又焕发出新的活力,JPA 提供 AttributeConverter 以对属性转换进行自定义。,首先,构建 JpaNewsStatusConverter,具体如下:,CommonEnumAttributeConverter 为 CommonEnum 提供的通用转化能力,具体如下:,在有了 JpaNewsStatusConverter 之后,我们需要在 Entity 的属性上增加配置信息,具体如下:,@Convert(converter =JpaNewsStatusConverter.class) 是对 status 的配置,使用 JpaNewsStatusConverter 进行属性的转换。,运行持久化指令后,数据库如下:,20230307014918d980d476388550d500562515d9906531d2b005894,项目仓库地址:https://gitee.com/litao851025/lego,项目文档地址:https://gitee.com/litao851025/lego/wikis/support/enums

© 版权声明

相关文章