springboot中枚举的使用

4,053 阅读5分钟

[故事背景]

由于之前自己一直在都开发其他的模块,最近开始加入到同事们的模块中,这一看不要紧,满屏的魔法数字让我差点喘不过气来。虽然也有些同事想到了使用静态变量来处理,但是明明就是一类的东西非要分开用不同的罐子来装它,不觉得别扭吗?于是决定记录一下之前自己使用枚举的经验;先贴一下魔法数字供各位客观欣赏

@Override
public ReturnObject putCanceUser(UcasCancellationBo ucasCancellationBo) {
    UcasCancellationInfo ucasCancellationInfo = new UcasCancellationInfo();
    ucasCancellationInfo.setId(ucasCancellationBo.getId());
    ucasCancellationInfo.setIsDelete(1);
    int count = ucasCancellationInfoMapper.updateById(ucasCancellationInfo);
    if(count>0){
        return ReturnObject.success();
    }else {
        return ReturnObject.fail("操作失败");
    }
}

[枚举映射数据库字段]

如果你也使用mybatis框架来做持久化层的话,那么可以使用mybatisplus的通用枚举功能,这里也有好几种用法;我只记录自己喜欢用的,其他方法可以自行查看官网

带我去

首先配置枚举包扫描路径

mybatis-plus:
  # 扫描通用枚举
  type-enums-package: com.xxjqr.rbac.enums

[方法一 @EnumValue与@JsonValue]

在枚举中使用注解 @EnumValue与@JsonValue

@Getter
@AllArgsConstructor
public enum SexEnum implements BaseEnum {
    MALE(1,"男"),
    FEMALE(0,"女"),
    UNKNOW(2,"未知")
    ;
    @EnumValue
    private final Integer code;
    @JsonValue
    private final String desc;
}

当SexEnum作为一个实体对象的属性时,那么该属性所映射的数据库字段就是@EnumValue作用的值

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class TbUser extends Model<TbUser> {

    private static final long serialVersionUID = 1L;

    /**
     * ID
     */
    private Long id;

    /**
     * 性别
     */
    private SexEnum sex;


    /**
     * 状态 0 禁用 1正常
     */
    private StatusEnum status;


    /**
     * 是否删除 1:已删除;0:未删除
     */
    @TableLogic
    private Integer isDelete;


    @Override
    protected Serializable pkVal() {
        return this.id;
    }

}

比如如下伪代码,就会把性别为0的girl插入数据库中

TbUser user = new TbUser(SexEnum.FEMALE)
userMapper.insert(user)

同样,当从数据库中读取数据时,数据库的值也会对应转换成相应的枚举对象

TbUser use = userMapper.selectById(id)
use.sex == SexEnum.FEMALE

如果该实体对象返回到前端,那么该枚举属性对应的值就是@JsonValue修饰的字段

[方法二 实现IEnum接口]

重写getValue等同于使用@EnumValue注解 重写toString等同于使用@JsonValue注解

@AllArgsConstructor
@Getter
//IEnum<Integer> Inteter泛型表示映射到数据库的字段类型
public enum BusinessStatusEnum implements IEnum<Integer>{
    FAIL(0, "失败"),
    SUCCESS(1, "成功"),
    ;

    private final Integer code;
    private final String desc;

    @Override
    public Integer getValue() {
        return code;
    }

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

}

然后还需要配置一下jackson的反序列化枚举 write_enums_using_to_string

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    serialization:
      write_enums_using_to_string: true

[反序列化参数转枚举]

现在代码中一部分的魔法数字已经被干掉了,但仍有一些顽疾是需要其他方式处理的。比如,前端在调用增删改查接口的时候,会传入类似 status='1',{'sex':1}之类的参数,那如何在这个接口接参(专业点说叫json反序列化)的过程中把魔法数字转换成枚举对象呢?

[一:get请求与post formdata传参]

这类的请求,我们可以直接使用spring提供的convert转换器来转换

[1] 定义公共枚举接口

public interface BaseEnum {
    Integer getCode();
}

[2] 继承公共枚举接口

@AllArgsConstructor
@Getter
public enum BusinessStatusEnum implements BaseEnum{
    FAIL(0, "失败"),
    SUCCESS(1, "成功"),
    ;

    @EnumValue
    private final Integer code;
    @JsonValue
    private final String desc;
}

@Getter
@AllArgsConstructor
public enum MenuTypeEnum implements BaseEnum {
    CATALOG(1,"目录"),
    MENU(2,"菜单"),
    BUTTON(3,"按钮");

    @EnumValue
    @JsonValue
    private final Integer code;
    private final String desc;
}

[3] 定义String->Enum的converter转换器

/*
因为我加了write_numbers_as_strings配置,所有数值类型全都转换成了string
spring:
  jackson:
    generator:
      write_numbers_as_strings: true
所以这里写的转换器是String->Enum
*/

public class StringToEnumConverter<T extends BaseEnum> implements Converter<String, BaseEnum> {
    private Map<String, T> enumMap = new HashMap<>();

    /**
     * @描述 StringToEnumConverter构造函数,起到缓存的作用,先把对应的code->enum映射关系缓存起来,匹配到了直接获取
     * @码农 丁昌江
     * @日期 2021/5/24 15:16
     */
    public StringToEnumConverter(Class<T> enumType) {
        //把所有枚举对象读取出来
        T[] enums = enumType.getEnumConstants();
        for (T e : enums) {
            //枚举对象的code作为key,枚举对象作为value
            enumMap.put(e.getCode().toString(), e);
        }
    }

    /**
     * @描述 code->enum 核心代码
     * @码农 丁昌江
     * @日期 2021/5/24 15:33
     */
    @Override
    public T convert(String source) {
        if (StrUtil.isEmpty(source)) {
            return null;
        }
        T t = enumMap.get(source);
        if (ObjectUtil.isNull(t)) {
            throw new IllegalArgumentException("无法匹配对应的枚举类型");
        }
        return t;
    }
}

[4] 定义对应的转换器工厂

public class StringToEnumConverterFactory implements ConverterFactory<String, BaseEnum> {

    private static final Map<Class, Converter> CONVERTERS = new HashMap<>();

    /**
     * 从转换器工厂中找到对应的转换器返回
     * @param targetType 转换后的类型
     * @return 返回一个转化器
     */
    @Override
    public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
        StringToEnumConverter converter = (StringToEnumConverter) CONVERTERS.get(targetType);
        if (converter == null) {
            converter = new StringToEnumConverter(targetType);
            CONVERTERS.put(targetType, converter);
        }
        return converter;
    }

}

[5] 将转换器工厂添加到springboot配置中

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 枚举类的转换器工厂 addConverterFactory
     */
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverterFactory(new StringToEnumConverterFactory());
    }
}

[拓展]

有时候会不会有这种需求呢,前端传过来的sex='FEMAIL',这种情况下如何转换成对应的枚举对象呢?

[1] 公共枚举接口添加getName()方法

public interface BaseEnum {
    Integer getCode();
    String getName();
}

[2] 枚举对象重写getName()

@Getter
@AllArgsConstructor
public enum SexEnum implements BaseEnum {
    MALE(1,"男"),
    FEMALE(0,"女"),
    UNKNOW(2,"未知")
    ;
    @EnumValue
    private final Integer code;
    @JsonValue
    private final String desc;

    @Override
    public String getName() {
        return name();
    }
}

[3] 转换器新增一个name->enum的缓存

public class StringToEnumConverter<T extends BaseEnum> implements Converter<String, BaseEnum> {
    private Map<String, T> enumMap = new HashMap<>();
    private Map<String, T> nameEnumMap = new HashMap<>();

    /**
     * @描述 StringToEnumConverter构造函数,起到缓存的作用,先把对应的code->enum映射关系缓存起来,匹配到了直接获取
     * @码农 丁昌江
     * @日期 2021/5/24 15:16
     */
    public StringToEnumConverter(Class<T> enumType) {
        //把所有枚举对象读取出来
        T[] enums = enumType.getEnumConstants();
        for (T e : enums) {
            //枚举对象的code作为key,枚举对象作为value
            enumMap.put(e.getCode().toString(), e);
            //枚举对象的name作为key,枚举对象作为value
            nameEnumMap.put(e.getName(), e);
        }
    }

    /**
     * @描述 code->enum 核心代码
     * @码农 丁昌江
     * @日期 2021/5/24 15:33
     */
    @Override
    public T convert(String source) {
        if (StrUtil.isEmpty(source)) {
            return null;
        }
        T t = enumMap.get(source);
        // 如果code->enum的缓存中无法获取,那么尝试从name->enum的缓存中获取
        if (ObjectUtil.isNull(t)) {
            t = nameEnumMap.get(source);
            if (ObjectUtil.isNull(source)) {
                throw new IllegalArgumentException("无法匹配对应的枚举类型");
            }
        }
        return t;
    }
}

[二:post json 传参]

当post请求使用json格式传参,也就是后端使用@RequstBody注解的时候,springboot默认使用的是jackson反序列化工具,那么我们就可以从jackson上安排



[1] 使用@JsonCreator注解可以自定义枚举创建方式

@Getter
@AllArgsConstructor
public enum StatusEnum implements BaseEnum{
    ENABLE(1,"正常"),
    DISABLE(0,"禁用")
    ;

    @EnumValue
    @JsonValue
    private final Integer code;
    private final String desc;


    //当版本降为2.10.5时能正常运行,2.11版本只能将@JsonCreator修改为@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static StatusEnum valueOf(int code) {
        //StatusEnum.values() == values()
        for (StatusEnum statusEnum : values()) {
            if (statusEnum.code == code) {
                return statusEnum;
            }
        }
        return null;
    }

    @Override
    public String getName() {
        return name();
    }
}

当参数为时,就能自动转换成相应的枚举对象

{

    "status":"0"
}

[拓展]

下面的文章来自知乎 ,讲述了更多的用法,但是目前鄙人没有这种需求,大家去看看吧!

带我去