前言

  • 新的一年即将来到,回首2023年,也是学习了许多,不断进步。今天带来的是项目中遇到的一个业务要求处理方法总结:项目具有很多的枚举类,而这些枚举类在前端页面中需要作为下拉框选项等组件被前端获取。为了后续获取枚举值更加方便快捷,我们在项目启动的时候将所有Java枚举类用一个hash存入Redis中,在提供一个接口,使得前端可以从Redis获取自己想要的枚举值。下文将讲解实现步骤。

实现项目启动时加载枚举值到Redis

1. 定义EnumInterface接口
  • 定义EnumInterface接口,实现该接口的枚举类会在后续被识别,在项目启动时加入redis
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface EnumInterface {

    String getCode();

    String getDesc();

    //枚举类描述 该值后续会与枚举类名拼接 成为Redis hash的key
    String enumDesc();
    }
    2. 创建EnumDTO
  • 创建EnumDTO,将存放每个枚举类对象,同一个枚举类的所有对象最终会放入同一个列表,作为Redis hash的value。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Data
    @ApiModel(value = "EnumDto", description = "【枚举值-枚举值DTO】")
    public class EnumDto implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "枚举值类型-对应枚举值类名")
    private String typeEn;

    @ApiModelProperty(value = "枚举值类型-对应枚举值类名的desc描述信息")
    private String typeCh = "";

    @ApiModelProperty(value = "枚举值类型-对应枚举值的code值")
    private String code;

    @ApiModelProperty(value = "枚举值类型-对应枚举值的desc值")
    private String desc;

    @ApiModelProperty(value = "枚举值类型-对应枚举实例的名字")
    private String enumName;

    }
    3. 创建ClassUtils工具类
  • 该类提供一个方法,通过Java反射获取某个包中所有的枚举类,将它们存入列表。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    @Slf4j
    public class ClassUtils {
    //为了方便管理,我们将枚举类放在同一个包下,这里写入自己项目中枚举类所在的包名。
    private static final String PACKAGE_NAME = "com.common.base.enums";

    public static List<Class<Enum>> getAllEnumClasses() throws ClassNotFoundException, IOException {
    List<Class<Enum>> list = new ArrayList<>();
    for (String className : getClassName(PACKAGE_NAME, true)) {
    Class<?> clazz = Class.forName(className);
    if (Enum.class.isAssignableFrom(clazz) && EnumInterface.class.isAssignableFrom(clazz)) {
    list.add((Class<Enum>) clazz);
    }
    }
    return list;
    }
    /**
    * 获取某包下的所有类
    * @param packageName 包名
    * @param childPackage 是否遍历子包
    */
    public static List<String> getClassName(String packageName, boolean childPackage) throws IOException {
    List<String> fileNames = null;
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    String packagePath = packageName.replace(".", "/");
    URL url = loader.getResource(packagePath);
    if (url != null) {
    String type = url.getProtocol();
    if (type.equals("file")) {
    fileNames = getClassNameByFile(url.getPath(), childPackage);
    }
    } else {
    fileNames = getClassNameByJars(((URLClassLoader) loader).getURLs(), packagePath, childPackage);
    }
    return fileNames;
    }
    /**
    * 从项目文件获取某包下所有类
    *
    */
    private static List<String> getClassNameByFile(String filePath, boolean childPackage) throws UnsupportedEncodingException {
    List<String> myClassName = new ArrayList<>();
    // 解决路径包含中文的情况
    filePath = java.net.URLDecoder.decode(filePath,"utf-8");
    File file = new File(filePath);
    boolean exists = file.exists();
    File[] childFiles = file.listFiles();
    assert childFiles != null;
    for (File childFile : childFiles) {
    if (childFile.isDirectory()) {
    if (childPackage) {
    myClassName.addAll(getClassNameByFile(childFile.getPath(), childPackage));
    }
    } else {
    String childFilePath = childFile.getPath();
    if (childFilePath.endsWith(".class")) {
    childFilePath = childFilePath.substring(childFilePath.indexOf("\\classes") + 9,
    childFilePath.lastIndexOf("."));
    childFilePath = childFilePath.replace("\\", ".");
    myClassName.add(childFilePath);
    }
    }
    }
    return myClassName;
    }
    }
    4. 创建EnumService接口
  • 创建EnumService接口,声明有关枚举类操作的接口
    1
    2
    3
    4
    5
    6
    public interface EnumService {
    /**
    * 扫描路径下的所有JAVA枚举类,并加载到redis缓存中
    */
    void scanAndLoadEnumClassToRedis();
    }
    5. 创建EnumServiceImpl
  • 创建EnumService实现类EnumServiceImpl,实现scanAndLoadEnumClassToRedis方法,该方法实现将枚举值加载到Redis中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    @Service
    @Slf4j
    public class EnumServiceImpl implements EnumService {
    @Autowired
    private RedisOperation redisOperation;
    @Override
    public void scanAndLoadEnumClassToRedis() {
    try {
    //声明Redis中存放的数据结构
    Map<String, List<EnumDto>> map = new HashMap<>();
    //先删除原来的枚举类key,RedisKeyConstant.SYSTEM_ENUMS_CACHE_KEY 为存放缓存的key字符串,在常量类中自己定义
    redisOperation.del(RedisKeyConstant.SYSTEM_ENUMS_CACHE_KEY);
    //通过ClassUtils得到枚举类名,反射得到属性,填充属性到EnumDto中
    ClassUtils.getAllEnumClasses().forEach(enumClass -> {
    try {
    EnumInterface[] enumInterfaces = (EnumInterface[]) enumClass.getMethod("values").invoke(new Object());
    Arrays.stream(enumInterfaces).forEach(enumInterface -> {
    EnumDto enumDto = new EnumDto();
    enumDto.setCode(enumInterface.getCode());
    enumDto.setTypeEn(enumClass.getName());
    enumDto.setTypeCh(enumInterface.enumDesc());
    enumDto.setDesc(enumInterface.getDesc());
    enumDto.setEnumName(((Enum) enumInterface).name());
    //hash的key:枚举值类名|枚举类描述信息
    //hash的value:List<EnumDto>
    String key = enumClass.getName() + "|" + enumInterface.enumDesc();
    if (null == map.get(key)) {
    List<EnumDto> list = new ArrayList<>();
    list.add(enumDto);
    map.put(key, list);
    } else {
    map.get(key).add(enumDto);
    }
    });
    } catch (Exception e) {
    log.error("加载JAVA枚举值失败", e);
    redisOperation.del(RedisKeyConstant.SYSTEM_ENUMS_CACHE_KEY);
    throw new ProcessException(CommonConstants.ENUM_PROCESSING_EXCEPTION, "加载JAVA枚举值到Reids中发生错误,请检查代码!");
    }
    });
    map.forEach((key, val) ->
    //存入Redis
    redisOperation.hset(RedisKeyConstant.SYSTEM_ENUMS_CACHE_KEY, key, val)
    );
    } catch (Exception e) {
    log.error("加载JAVA枚举值失败", e);
    throw new ProcessException(CommonConstants.ENUM_PROCESSING_EXCEPTION, "加载JAVA枚举值到Reids中发生错误,请检查代码!");
    }
    }
    }
    6. 修改枚举类
  • 让需要加载到Redis中的枚举类实现EnumInterface接口,记得重写enumDesc方法,给枚举类们加上描述,这个描述很重要,以下是一个例子。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    public enum OperationTypeEnum implements EnumInterface {
    UPDATE("update", "更新"),
    DELETE("delete", "删除"),
    ADD("add","增加"),
    GET("show","展示");

    private final String code;
    private final String desc;

    OperationTypeEnum(String code, String desc) {
    this.code = code;
    this.desc = desc;
    }

    public String getCode() {
    return code;
    }

    @Override
    public String enumDesc() {
    return "操作类型";
    }

    @Override
    public String getDesc() {
    return this.desc;
    }

    public static String getDesc(String code) {
    for (OperationTypeEnum publishEnum : OperationTypeEnum.values()) {
    if (publishEnum.getCode().equals(code)) {
    return publishEnum.getDesc();
    }
    }
    return null;
    }

    public static OperationTypeEnum getByCode(String code) {
    for (OperationTypeEnum examSourceTypeEnum : OperationTypeEnum.values()) {
    if (examSourceTypeEnum.getCode().equals(code)) {
    return examSourceTypeEnum;
    }
    }
    return null;
    }
    }
    7. 创建ApplicationInit
  • ContextRefreshedEvent是Spring内置的上下文更新事件,该事件会在ApplicationContext被初始化或者更新时发布。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    * 枚举值初始化
    * 系统启动的时候,将枚举值全部刷新一遍
    */
    @Slf4j
    @Component
    public class ApplicationInit implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private EnumService enumService;
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
    log.info("================开始初始化系统数据===========");
    log.info("开始加载JAVA枚举值列表");
    enumService.scanAndLoadEnumClassToRedis();
    log.info("加载枚举值列表完成");
    log.info("================初始化系统数据结束===========");
    }
    }

测试结果

  • 上面的步骤完成后,就可以启动项目了,查看日志结果了:
    在这里插入图片描述

  • 日志成功打印之后,进入Redis可视化工具,可以看到Redis的枚举值已经存进去了,大功告成。
    在这里插入图片描述