如果变量值仅有有限的可选值,那么用枚举类来定义常量是一个很常规的操作。

但是在业务代码中,我们不希望依赖 ordinary() 进行业务运算,而是自定义数字属性,避免枚举值的增减调序造成影响。

@Getter
@AllArgsConstructor
public enum CourseType {PICTURE(102, "图文"),AUDIO(103, "音频"),VIDEO(104, "视频"),;private final int index;private final String name;
}

但也正是因为使用了自定义的数字属性,很多框架自带的枚举转化功能也就不再适用了。因此,我们需要自己来扩展相应的转化机制,这其中包括:

  1. SpringMVC 枚举转换器

  2. ORM 枚举映射

  3. JSON 序列化和反序列化

自定义 SpringMVC 枚举转换器

明确需求

以上文的 CourseType 为例,我们希望达到的效果是:

前端传参时给我们枚举的 index 值,在 controller 中,我们可以直接使用 CourseType 来接收,由框架负责完成 index 到 CourseType 的转换。

@GetMapping("/list")
public void list(@RequestParam CourseType courseType) {// do something
}

SpringMVC 自带枚举转换器

SpringMVC 自带了两个和枚举相关的转换器:

  • org.springframework.core.convert.support.StringToEnumConverterFactory

  • org.springframework.boot.convert.StringToEnumIgnoringCaseConverterFactory

这两个转换器是通过调用枚举的 valueOf 方法来进行转换的,感兴趣的同学可以自行查阅源码。

实现自定义枚举转换器

虽然这两个转换器不能满足我们的需求,但它也给我们带来了思路,我们可以通过模仿这两个转换器来实现我们的需求:

  1. 实现 ConverterFactory 接口,该接口要求我们返回 Converter,这是一个典型的工厂设计模式

  2. 实现 Converter 接口,完成自定义数字属性到枚举类的转化

废话不多说,上源码:

/*** springMVC 枚举类的转换器* 如果枚举类中有工厂方法(静态方法)被标记为{@link EnumConvertMethod },则调用该方法转为枚举对象*/
@SuppressWarnings("all")
public class EnumMvcConverterFactory implements ConverterFactory<String, Enum<?>> {private final ConcurrentMap<Class<? extends Enum<?>>, EnumMvcConverterHolder> holderMapper = new ConcurrentHashMap<>();@Overridepublic <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {EnumMvcConverterHolder holder = holderMapper.computeIfAbsent(targetType, EnumMvcConverterHolder::createHolder);return (Converter<String, T>) holder.converter;}@AllArgsConstructorstatic class EnumMvcConverterHolder {@Nullablefinal EnumMvcConverter<?> converter;static EnumMvcConverterHolder createHolder(Class<?> targetType) {List<Method> methodList = MethodUtils.getMethodsListWithAnnotation(targetType, EnumConvertMethod.class, false, true);if (CollectionUtils.isEmpty(methodList)) {return new EnumMvcConverterHolder(null);}Assert.isTrue(methodList.size() == 1, "@EnumConvertMethod 只能标记在一个工厂方法(静态方法)上");Method method = methodList.get(0);Assert.isTrue(Modifier.isStatic(method.getModifiers()), "@EnumConvertMethod 只能标记在工厂方法(静态方法)上");return new EnumMvcConverterHolder(new EnumMvcConverter<>(method));}}static class EnumMvcConverter<T extends Enum<T>> implements Converter<String, T> {private final Method method;public EnumMvcConverter(Method method) {this.method = method;this.method.setAccessible(true);}@Overridepublic T convert(String source) {if (source.isEmpty()) {// reset the enum value to null.return null;}try {return (T) method.invoke(null, Integer.valueOf(source));} catch (Exception e) {throw new IllegalArgumentException(e);}}}}
  • EnumMvcConverterFactory :工厂类,用于创建 EnumMvcConverter

  • EnumMvcConverter:自定义枚举转换器,完成自定义数字属性到枚举类的转化

  • EnumConvertMethod:自定义注解,在自定义枚举类的工厂方法上标记该注解,用于 EnumMvcConverter 来进行枚举转换

EnumConvertMethod 的具体源码如下:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnumConvertMethod {
}

怎么使用

1、注册 EnumMvcConverterFactory

@Configuration
public class MvcConfiguration implements WebMvcConfigurer {@Beanpublic EnumMvcConverterFactory enumMvcConverterFactory() {return new EnumMvcConverterFactory();}@Overridepublic void addFormatters(FormatterRegistry registry) {// org.springframework.core.convert.support.GenericConversionService.ConvertersForPair.add// this.converters.addFirst(converter);// 所以我们自定义的会放在前面registry.addConverterFactory(enumMvcConverterFactory());}
}

2、在自定义枚举中提供一个工厂方法,完成自定义数字属性到枚举类的转化,同时在该工厂方法上添加 @EnumConvertMethod 注解

@Getter
@AllArgsConstructor
public enum CourseType {PICTURE(102, "图文"),AUDIO(103, "音频"),VIDEO(104, "视频"),;private final int index;private final String name;private static final Map<Integer, CourseType> mappings;static {Map<Integer, CourseType> temp = new HashMap<>();for (CourseType courseType : values()) {temp.put(courseType.index, courseType);}mappings = Collections.unmodifiableMap(temp);}@EnumConvertMethod@Nullablepublic static CourseType resolve(int index) {return mappings.get(index);}
}

自定义 ORM 枚举映射

遇到什么问题

还是以上述的 CourseType 枚举为例,一般业务代码的数据都要持久化到 DB 中的。假设,现在有一张课程元数据表,用于记录当前课程所属的类型,我们的 entity 对象可能是这样的:

@Getter
@Setter
@Entity
@Table(name = "course_meta")
public class CourseMeta {private Integer id;/*** 课程类型,{@link CourseType}*/private Integer type;
}

上述做法是通过 javadoc 注释的方式来告诉使用方 type 的取值类型是被关联到了 CourseType。

但是,我们希望通过更清晰的代码来避免注释,让代码不言自明

因此,能不能让 ORM 在映射的时候,直接把 Integer 类型的 type 映射成 CourseType 枚举呢?答案是可行的。

AttributeConverter

我们当前系统使用的是 Spring Data JPA 框架,是对 JPA 的进一步封装。因此,本文只提供在 JPA 环境下的解决方案。

在 JPA 规范中,提供了 javax.persistence.AttributeConverter 接口,用于扩展对象属性和数据库字段类型的映射。

public class CourseTypeEnumConverter implements AttributeConverter<CourseType, Integer> {@Overridepublic Integer convertToDatabaseColumn(CourseType attribute) {return attribute.getIndex();}@Overridepublic CourseType convertToEntityAttribute(Integer dbData) {return CourseType.resolve(dbData);}
}

怎么生效呢?有两种方式

  1. 将 AttributeConverter 注册到全局 JPA 容器中,此时需要与 javax.persistence.Converter 配合使用

  2. 第二种方式是配合 javax.persistence.Convert 使用,在需要的地方指定 AttributeConverter,此时不会全局生效

本文选择的是第二种方式,在需要的地方指定 AttributeConverter,具体代码如下:

@Getter
@Setter
@Entity
@Table(name = "ourse_meta")
public class CourseMeta {private Integer id;@Convert(converter = CourseTypeEnumConverter.class)private CourseType type;
}

JSON 序列化

到这里,我们已经解决了 SpringMVC 和 ORM 对自定义枚举的支持,那是不是这样就足够了呢?还有什么问题呢?

SpringMVC 的枚举转化器只能支持 GET 请求的参数转化,如果前端提交 JSON 格式的 POST 请求,那还是不支持的。

另外,在给前端输出 VO 时,默认情况下,还是要手动把枚举类型映射成 Integer 类型,并不能在 VO 中直接使用枚举输出。

@Data
public class CourseMetaShowVO {private Integer id;private Integer type;public static CourseMetaShowVO of(CourseMeta courseMeta) {if (courseMeta == null) {return null;}CourseMetaShowVO vo = new CourseMetaShowVO();vo.setId(courseMeta.getId());// 手动转化枚举vo.setType(courseMeta.getType().getIndex());return vo;}
}

@JsonValue 和 @JsonCreator

Jackson 是一个非常强大的 JSON 序列化工具,SpringMVC 默认也是使用 Jackson 作为其 JSON 转换器。

Jackson 为我们提供了两个注解,刚好可以解决这个问题。

  • @JsonValue:在序列化时,只序列化 @JsonValue 注解标注的值

  • @JsonCreator:在反序列化时,调用 @JsonCreator 标注的构造器或者工厂方法来创建对象

最后的代码如下:

@Getter
@AllArgsConstructor
public enum CourseType {PICTURE(102, "图文"),AUDIO(103, "音频"),VIDEO(104, "视频"),;@JsonValueprivate final int index;private final String name;private static final Map<Integer, CourseType> mappings;static {Map<Integer, CourseType> temp = new HashMap<>();for (CourseType courseType : values()) {temp.put(courseType.index, courseType);}mappings = Collections.unmodifiableMap(temp);}@EnumConvertMethod@JsonCreator(mode = JsonCreator.Mode.DELEGATING)@Nullablepublic static CourseType resolve(int index) {return mappings.get(index);}
}

扩展 swagger 对枚举的支持

经过上述的一些自定义转换器,基本解决了在代码中使用枚举的一些痛点。但是,你以为这就够了吗?

现在大部分的代码都在使用 swagger 来编写文档,不知道大家有没有这样的痛点:

在编写文档时,需要告诉前端枚举类型有哪些取值,每次增加取值之后,不仅要改代码,还要找到对应的取值在哪里使用了,然后修改 swagger 文档。

反正小黑我觉得这样做很不爽,那有没有什么办法可以让 swagger 框架来帮我们自动列举出所有的枚举数值呢?办法当然是有的啦!

怎么做呢?emmm... 这个我们下期揭晓~~

一站式解决使用枚举的各种痛点相关推荐

  1. 一站式解决健身房经营的五大痛点

    当前,健身房市场发展迅速,场馆数量激增,然而,另一方面,诸多健身房也面临着经营困难.亏损甚至倒闭的局面,这种极大的反差使得经营者不得不寻求更合适的经营模式. 而如今,必须要承认的是,健身房会员管理系统 ...

  2. 区块链技术解决投行电子底稿监管痛点 中国证券业协会在“中证链”发布首个应用

    区块链作为一项新兴技术,其价值正得到普遍认可.并且在诸多领域都有应用潜力,其中金融领域因具备参与者之间信任度低.交易记录安全性和完备性要求高的特点,与区块链天然契合.而证券行业作为金融体系的重要组成, ...

  3. Linux 桌面玩家指南:17. 在 Ubuntu 中使用 deepin-wine,解决一些依赖 Windows 的痛点问题

    Linux 桌面玩家指南:17. 在 Ubuntu 中使用 deepin-wine,解决一些依赖 Windows 的痛点问题 参考文章: (1)Linux 桌面玩家指南:17. 在 Ubuntu 中使 ...

  4. php yii2 sns,GitHub - yggphpcoder/iisns: 基于 yii2 的 sns 社区系统,一站式解决社区建站...

    iisns - 地球村入口 iiSNS 是基于 yii2 的 SNS 社区系统,一站式解决社区建站.可以写文章,做记录,上传图片,论坛聊天等.还可以用来做内容管理系统(CMS).iiSNS 是一个免费 ...

  5. Supervisor安装与配置问题一站式解决

    Supervisor安装与配置问题一站式解决 参考文章: (1)Supervisor安装与配置问题一站式解决 (2)https://www.cnblogs.com/onlyzc/p/10033832. ...

  6. 低代码究竟能干啥?三位一体解决数字化管理的难点痛点

    编者按:面对数字化时代对管理工作提出的更高要求,没有企业能够在进步的路上一帆风顺!数字化管理出现无数难点痛点,如何能够顺利解决?本文对此进行分析,介绍低代码平台"三位一体"如何帮助 ...

  7. php yii2 sns,GitHub - szhsh/iisns: 基于 yii2 的 sns 社区系统,一站式解决社区建站

    iisns - 地球村入口 iiSNS 是基于 yii2 的 SNS 社区系统,一站式解决社区建站.可以写文章,做记录,上传图片,论坛聊天等.还可以用来做内容管理系统(CMS).iiSNS 是一个免费 ...

  8. php yii2 sns,GitHub - 13240031972/iisns: 基于 yii2 的 sns 社区系统,一站式解决社区建站...

    iisns - 地球村入口 iiSNS 是基于 yii2 的 SNS 社区系统,一站式解决社区建站.可以写文章,做记录,上传图片,论坛聊天等.还可以用来做内容管理系统(CMS).iiSNS 是一个免费 ...

  9. php yii2 sns,GitHub - zhaojizi/iisns: 基于yii2的sns社区系统,一站式解决社区建站

    iisns - 地球村入口 iiSNS 是基于 yii2 的 SNS 社区系统,一站式解决社区建站.可以写文章,做记录,上传图片,论坛聊天等.还可以用来做内容管理系统(CMS).iiSNS 是一个免费 ...

最新文章

  1. 第一讲 IDLE使用
  2. 学python有哪些书推荐-Python 有哪些入门学习方法和值得推荐的经典教材?
  3. Oracle11G数据泵支持,Oracle11gr2数据泵新特性(五)
  4. 事编计算机网络管理专技如何评级,事业单位管理和专技岗位有什么区别?哪个有前途?...
  5. python 数据去重_科学网—python学习——根据条件提取数据,并去重 - 李立的博文...
  6. [react] 有在项目中使用过Antd吗?说说它的好处
  7. 【网络流24题】No.7 试题库问题 (最大流,二分图多重匹配)
  8. __decspec(selectany)
  9. 对人工智能产品发展的几点认识
  10. 《『若水新闻』客户端开发教程》——12.代码编写(4)
  11. 游戏筑基开发之二进制文件操作的那点事儿(C语言)
  12. Oracle生成流水号函数
  13. 针对Web系统常用的功能测试方法浅析
  14. ISO体系认证是什么意思/ISO9001质量体系认证有什么用
  15. 深入Bert实战(Pytorch)----WordPiece Embeddings
  16. CWnd::WindowProc的理解
  17. 「囚徒困境」有哪些现实的例子?
  18. matlab bsxfun memory,matlab – BSXFUN关于关系操作的内存效率
  19. Maven 父pom中dependencyManagement版本优先级高于传递依赖版本
  20. P228 10.2.2 希尔排序 完整源文件

热门文章

  1. 【python图像处理】图像的读取、显示与保存
  2. 2017年 JavaScript 框架回顾 -- 前端框架
  3. 替换文件中的某个内容
  4. JavaScript的undefined
  5. Lync前端服务器的卸载
  6. ADO.NET开发总结(学习笔记)
  7. OpenCV 为图像转换为漫画效果
  8. WebSocket 从入门到写出开源库
  9. linux命令之修改系统允许进程打开文件描述符限制-ulimit
  10. Windows下一个比较完美的线程池实现