如何统一管理枚举类?

2,772 阅读4分钟

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 中 直接定义为枚举类型就可以了,是不是非常简单呢?

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