如何统一管理枚举类?
Hello,大家好,今天我们来聊一下关于系统中的枚举是如何统一进行管理的。
业务场景
我们公司有这样的一个业务场景前端表单中 下拉选择的枚举值,是需要从后端获取的。那么这时候有个问题,我们不可能每次新增加一个枚举,都需要 改造获取枚举 的相关接口(getEnum),所以我们就需要对系统中的所有枚举类,进行统一的一个管理。
核心思路
为了解决这个问题,我们采用了如下的方案
- 当服务启动时,统一对 枚举类 进行 注册发现
- 枚举管理类,对外暴露一个方法,可以根据我的key 去获取对应的枚举值
相关实现
枚举定义
基于以上的思想,我们对枚举类定义了如下的规范,例如
@**IsEnum**
public enum BooleanEnum implements BaseEnums {
YES("是", "1"),
NO("否", "2")
;
private String text;
@EnumValue
private String value;
YesNoEnum(String text, String value) {
this.text = text;
this.value = value;
}
public String getText() {
return text;
}
@Override
public String getValue() {
return value;
}
@Override
public String toString() {
return "YesNoEnum{" +
"text='" + text + '\'' +
", value=" + value +
'}';
}
@Override
public EnumRequest toJson() {
return new EnumRequest(value, text);
}
@JsonCreator
public static YesNoEnum fromCode(String value) {
for (YesNoEnum status : YesNoEnum.values()) {
if (status.value.equals(value)) {
return status;
}
}
return null; // 或抛出异常
}
}
- 所有枚举均使用 @IsEnum进行标记(这是一个自定义注解)
- 所有枚举均实现 BaseEnums 接口(具体作用后续会提到)
- 所有的枚举的 value 值 统一使用 String 类型,并使用
@EnumValue
注解进行标记- 这主要是为了兼容Mybatis Plus 查表时 基础数据类型与枚举类型的转化
- 如果将
value
定义为Object类型,Mybatis Plus 无法正确识别,会报错
- 使用
@JsonCreator
注解标记转化函数- 这个注解是有Jackson提供的,使用了从 基础数据类型到枚举类型的转化
注册发现
那么我们是如何实现服务的注册发现的呢?在这里主要是 使用了 SpringBoot 提供的接口CommandLineRunner
(关于CommandLineRunner
可以参考这篇文章https://blog.csdn.net/python113/article/details/109244712)
在应用启动时,我们回去扫描 枚举所在的 包,通过 类上 是否包含 IsEnum
注解,判断是否需要对外暴露
// 指定要扫描的包
String basePackage = "com.xxx.enums";
// 创建扫描器
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(EnumMarker.class));
// 扫描指定包
Set<BeanDefinition> beans = scanner.findCandidateComponents(basePackage);
// 注册枚举
for (org.springframework.beans.factory.config.BeanDefinition beanDefinition : beans) {
try {
Class<?> enumClass = Class.forName(beanDefinition.getBeanClassName());
if (Enum.class.isAssignableFrom(enumClass)) {
enumManager.registerEnum((Class<? extends Enum<?>>) enumClass);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
ClassPathScanningCandidateComponentProvider
是 Spring 框架中的一个类,主要用于扫描指定路径下符合条件的候选组件(通常是 Bean 定义)。它允许我们在类路径中扫描并筛选符合特定条件(如标注特定注解、实现某接口等)的类,以实现自动化配置、依赖注入等功能。
典型用途
在基于注解的 Spring 应用中,我们可以使用它来动态扫描特定包路径下的类并注册成 Spring Bean。例如,Spring 扫描 @Component
、@Service
、@Repository
等注解标注的类时就会用到它。
主要方法
addIncludeFilter(TypeFilter filter)
:添加一个包含过滤器,用于筛选扫描过程中包含的类。findCandidateComponents(String basePackage)
:扫描指定包路径下的候选组件(符合条件的类),并返回符合条件的BeanDefinition
对象集合。addExcludeFilter(TypeFilter filter)
:添加一个排除过滤器,用于排除特定类。
最终呢,会将找到的枚举值,放在一个EnumManager中的一个Map集合中
private final Map<Class<?>, List<Enum<?>>> enumMap = new HashMap<>(); // 类与枚举类型的映射关系
private final Map<String, List<Enum<?>>> enumNameMap = new HashMap<>(); // 名称与枚举的映射管理
public <E extends Enum<E>> void registerEnum(Class<? extends Enum<?>> enumClass) {
Enum<?>[] enumConstants = enumClass.getEnumConstants();
final List<Enum<?>> list = Arrays.asList(enumConstants);
enumMap.put(enumClass, list);
enumNameMap.put(enumClass.getSimpleName(), list);
}
enumClass.getEnumConstants()
是 Java 反射 API 中的一个方法,用于获取某个枚举类中定义的所有枚举实例。getEnumConstants()
会返回一个包含所有枚举常量的数组,每个元素都是该枚举类的一个实例。
这样子我们就可以通过枚举的名称或者class 获取枚举列表返回给前端
enumMap.get(enumClass); enumNameMap.get(enumName);
请求与响应
我们项目中使用的序列化器 是Jackson,通过 @JsonValue
与@JsonCreator
两个注解实现的。
@JsonValue
:对象序列化为json时会调用这个注解标记的方法
@JsonValue
public EnumRequest toJson() {
return new EnumRequest(value, text);
}
@JsonCreator
:json反序列化对象时会调用这个注解标记的方法
@JsonCreator
public static YesNoEnum fromCode(String value) {
for (YesNoEnum status : YesNoEnum.values()) {
if (status.value.equals(value)) {
return status;
}
}
return null; // 或抛出异常
}
但是这里有个坑,我们SpringBoot的版本是2.5,使用 @JsonCreator
时会报错,这时候只需要降低jackson 的版本就可以了
// 排除 spring-boot-starter-web 中的jsckson
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.10.5</version>
</dependency>
Mybatis Plus 枚举中的使用
- 在applicaton.yml文件中添加如下参数
# 在控制台输出sql
mybatis-plus:
type-enums-package: com.xxx.enums // 定义枚举的扫描包
- 将枚举值中 存入到数据库的字段 使用
@EnumValue
注解进行标记,例如:上面提供的YesNoEnum
类中的value字段使用@EnumValue
进行了标记 数据库中实际保存的就是 value 的值(1/2) - 将domian 中 直接定义为枚举类型就可以了,是不是非常简单呢?
以上就是本篇文章的全部内容,如果有更好的方案欢迎小伙伴们评论区讨论!