枚举字典最佳实践

枚举是 Java 开发中必不可少的一部分,工作中常用来定义单例工具类,常量,状态字典等,本文主要介绍枚举当做字典的最佳实践

一、问题背景

一直在做前后端分离开发的同学应该都遇到过这样一个问题,比如有个字段性别,有男,女,未知,前端是下拉选传递男 1,女 2,未知 3 过来,后端存入数据库的时候都是存 0,1,2 枚举值,这样的话如果后端查询相关性别数据的时候只能查到 0,1,2 这样的数据,并不能返回对应的描述字段,如果前端也要具体的描述字段该怎么办呢?有两种方案

  1. 前端写死字典表,后端循环重新组装数据给前端
  2. 提供前端字典表接口,后端只传递 code 值,具体的 name 描述字段由前端转换

之前公司都是采用第一种方案,数据都由后端提供,开发迅速,免得跟前端扯皮,但是使用一方案有个问题:如果我想增加一种枚举,那岂不是前端后端的字典表都要改

其实我个人觉得第二种方案更合理,但是作为单体项目,我又不想创建字表相关的表,想直接使用枚举类当做字典表返回给前端,岂不美哉,所以就有了以下的想法:

把程序中需要提供给前端的枚举类存入到缓存,给前端提供字典查询接口,这样即使新增枚举也只要改后端就可以了

二、最佳实践

1. 枚举抽象类

public interface BaseCodeEnum<T> {

    T getCode();

    String getName();

}
  • 限制枚举字段约束,为了之后的枚举工具类做准备
/**
 * @author: sunhhw
 * @date: 2023/12/8 12:25
 * @description: 字典枚举值标记
 */
public interface DictEnumAware<T> extends BaseCodeEnum<T> {

}
  • Aware 结尾标记,标识实现该接口的枚举是需要当做字典返回给前端的
public enum ArticleStatusEnum implements DictEnumAware<Integer> {

    PUBLISH(1, "发布"),
    DRAFTS(0, "草稿箱"),
    ;

    private final Integer code;
    private final String name;

    ArticleStatusEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    @Override
    public Integer getCode() {
        return this.code;
    }

    @Override
    public String getName() {
        return this.name;
    }
}
  • 如果只是程序内部使用,则实现 BaseCodeEnum 类即可

2. 扫描枚举类

public enum EnumUtils {

    /**
     * 枚举标记
     */
    X;

       /**
     * 根据枚举class获取该枚举的所有code,name列表
     */
    public <T extends DictEnumAware<N>, N> DictEnumDTO getEnumList(Class<T> enumClass) {
        DictEnumDTO dictEnumDTO = new DictEnumDTO();
        List<DictDTO> dictDTOList = new ArrayList<>();
        for (T typeEnum : enumClass.getEnumConstants()) {
            DictDTO dictDTO = new DictDTO();
            N code = typeEnum.getCode();
            String name = typeEnum.getName();
            dictDTO.setCode(code.toString());
            dictDTO.setName(name);
            dictDTOList.add(dictDTO);
        }
        dictEnumDTO.setDictDTOList(dictDTOList);
        dictEnumDTO.setDictName(enumClass.getSimpleName());
        return dictEnumDTO;
    }


    public List<DictEnumDTO> registry(String basePackage) {
        List<DictEnumDTO> dictEnumDTOList = new ArrayList<>();
        try {
            PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(resourcePatternResolver);
            String packageSearchPath = "classpath*:" + basePackage.replace('.', '/') + "/**/*.class";
            Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);

            for (Resource resource : resources) {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                ClassMetadata classMetadata = metadataReader.getClassMetadata();
                if (classMetadata.isInterface() || classMetadata.isAbstract()) {
                    // 接口或抽象类,不处理
                    continue;
                }
                String[] interfaceNames = classMetadata.getInterfaceNames();
                for (String interfaceName : interfaceNames) {
                    if (interfaceName.equals(DictEnumAware.class.getName())) {
                        String className = classMetadata.getClassName();
                        Class<? extends DictEnumAware<Object>> clazz = (Class<? extends DictEnumAware<Object>>) Class.forName(className);
                        DictEnumDTO dictEnumDTO = getEnumList(clazz);
                        dictEnumDTOList.add(dictEnumDTO);
                        break;
                    }
                }
            }
        } catch (Exception e) {
            throw new ServiceException("字典类型初始化失败", e);
        }
        return dictEnumDTOList;
    }
}
  • 其中部分 DTO 类自行补充
  • 使用PathMatchingResourcePatternResolverMetadataReaderFactory扫描指定包路径下的类,并筛选出该类是否实现DictEnumAware接口,找出对应的枚举类
  • 然后获取该枚举类的 code 和 name 值,封装到 DTO 对象中

3.存入缓存

@Test
public void test_01() {
    String packageName = "com.blog";
    List<DictEnumDTO> dictEnumDTOList = EnumUtils.X.registry(packageName);
    for (DictEnumDTO dictEnumDTO : dictEnumDTOList) {
        // 存入缓存
    }
}

4.提供接口

需要提供两个接口,一个是根据枚举名称查询所有枚举列表,一个是根据枚举名称和枚举 code 查询枚举 name


我是一零贰肆,一个关注Java技术和记录生活的博主。

欢迎扫码关注“一零贰肆的公众号”,一起学习,共同进步,多看路,少踩坑。

在这里插入图片描述

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值