注册

如何统一管理枚举类?

Hello,大家好,今天我们来聊一下关于系统中的枚举是如何统一进行管理的。


业务场景


我们公司有这样的一个业务场景前端表单中 下拉选择的枚举值,是需要从后端获取的。那么这时候有个问题,我们不可能每次新增加一个枚举,都需要 改造获取枚举 的相关接口(getEnum),所以我们就需要对系统中的所有枚举类,进行统一的一个管理。


核心思路


为了解决这个问题,我们采用了如下的方案



  1. 当服务启动时,统一对 枚举类 进行 注册发现
  2. 枚举管理类,对外暴露一个方法,可以根据我的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; // 或抛出异常
}
}


  1. 所有枚举均使用 @IsEnum进行标记(这是一个自定义注解)
  2. 所有枚举均实现 BaseEnums 接口(具体作用后续会提到)
  3. 所有的枚举的 value 值 统一使用 String 类型,并使用 @EnumValue 注解进行标记

    1. 这主要是为了兼容Mybatis Plus 查表时 基础数据类型与枚举类型的转化
    2. 如果将 value 定义为Object类型,Mybatis Plus 无法正确识别,会报错


  4. 使用 @JsonCreator 注解标记转化函数

    1. 这个注解是有Jackson提供的,使用了从 基础数据类型到枚举类型的转化



注册发现


那么我们是如何实现服务的注册发现的呢?在这里主要是 使用了 SpringBoot 提供的接口CommandLineRunner (关于CommandLineRunner 可以参考这篇文章blog.csdn.net/python113/a…


在应用启动时,我们回去扫描 枚举所在的 包,通过 类上 是否包含 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 中 直接定义为枚举类型就可以了,是不是非常简单呢?

以上就是本篇文章的全部内容,如果有更好的方案欢迎小伙伴们评论区讨论!


作者:学编程的小菜鸟
来源:juejin.cn/post/7431838327844995098

0 个评论

要回复文章请先登录注册