枚举是 Java 开发中必不可少的一部分,常用来定义单例工具类,常量,状态字典等,本文主要介绍枚举当做字典的最佳实践
一、问题背景
一直在做前后端分离开发的同学应该都遇到过这样一个问题,比如有个字段性别,有男,女,未知,前端是下拉选传递男 1,女 2,未知 3 过来,后端存入数据库的时候都是存 0,1,2 枚举值,这样的话如果后端查询相关性别数据的时候只能查到 0,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 类自行补充 -
使用 PathMatchingResourcePatternResolver
,MetadataReaderFactory
扫描指定包路径下的类,并筛选出该类是否实现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枚举最佳实践
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/250567.html