springboot-枚举


目录

  • springboot-枚举
  • 前言
  • 自定义枚举转换
  • 定义枚举
  • 表单参数
  • Json参数
  • mybatis


前言

在开发中,有时候我们会使用枚举来作为参数和返回值,在springboot中,默认情况枚举类型使用规则如下:

对于json参数:

  • 数字,则对应定义顺序;
  • 字符串:
  • 如果枚举类中没有@JsonValue注解方法,则对应枚举类型的定义名称;
  • 如果有,则对应注解方法的返回值;
  • 如果没有找到对应的枚举类型,则抛出异常;

对于表单参数:

  • 只能是枚举类型的定义名称,其它的非空值都会抛出异常;

对于mybatis侧:

  • 枚举不管是作为参数值还是返回值,对应的都是定义的名称;

但是有的时候,枚举类的定义是编码/值的形式,期望参数传递,和数据库中保存的值应该是编码,而不是枚举的定义的变量名称,则需要自定义枚举类的处;

自定义枚举转换

定义枚举

定义BaseEnum接口,主要用于标记枚举需要使用自定义的转换,可以定义一些通用的自定义操作:

@JsonDeserialize(using = BaseEnumDeserializer.class)
public interface BaseEnum<K> {
    K getCode();
    String getName();
    @JsonValue
    default String toJson(){
        return getName();
    }
}

定义枚举类的同时实现BaseEnum接口,标记该枚举类使用自定义的转换:

@AllArgsConstructor
public enum Sex implements BaseEnum<Integer> {
    BOY(1, "男"), GIRL(2, "女");
    @Getter
    private Byte code;
    @Getter
    private String name;
}

表单参数

对于表单提交的参数,可以通过实现ConverterFactory接口来实现自定义转换,该类的定义方式可以参考默认的实现类StringToEnumConverterFactory

@SuppressWarnings({"rawtypes", "unchecked"})
@Component
public class BaseEnumConvert implements ConverterFactory<String, BaseEnum> {
    @Override
    public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToBaseEnum(targetType);
    }
    private static class StringToBaseEnum<T extends BaseEnum> implements Converter<String, T> {
        private final Class<T> enumType;
        StringToBaseEnum(Class<T> enumType) {
            this.enumType = enumType;
        }
        @Override
        public T convert(String source) {
            if (StringUtils.isNotBlank(source)) {
                T[] enums = this.enumType.getEnumConstants();
                for (T e : enums) {
                    if(e.getCode().toString().equals(source) || e.getName().equals(source)){
                        return e;
                    }
                }
            }
            return null;
        }
    }
}

在webMvcConfig配置类中注册这个ConverterFactory

@Override
public void addFormatters(FormatterRegistry registry) {
    registry.addConverterFactory(baseEnumConvert);
}

到此,get方式的参数和表单方式提交的参数就可以正确使用自定义的转换类了!

Json参数

通过实现JsonDeserializerContextualDeserializer接口定义Json的反序化类(该类定义可以参考默认实现类EnumDeserializer):

public class BaseEnumDeserializer extends JsonDeserializer<BaseEnum> implements ContextualDeserializer {
    private BaseEnum[] enums;
    @Override
    public BaseEnum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String val = p.getText();
        for (BaseEnum e : enums) {
            if (e.getCode().toString().equals(val) || e.getName().equals(val)) {
                return e;
            }
        }
        return null;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
        this.enums = (BaseEnum[]) property.getType().getRawClass().getEnumConstants();
        return this;
    }
}

接着在上面的BaseEnum类上添加注解:@JsonDeserialize(using = BaseEnumDeserializer.class)

到此,Json参数也就可以使用自定义的枚举转换类了!

mybatis

mybatis侧则是通过自定义Handler继承BaseTypeHandler抽象类,并添加@MappedTypes注解实现自定义操作的,具体如下:

@MappedTypes({BaseEnum.class})
public class EnumHandler<E extends BaseEnum> extends BaseTypeHandler<E> {
    private Map<String, E> map;
    public EnumHandler(Class<E> type) {
        E[] enums = type.getEnumConstants();
        if (enums != null) {
            map = Maps.newHashMapWithExpectedSize(enums.length);
            for (E e : enums) {
                map.put(e.getCode().toString(), e);
            }
        } else {
            map = new HashMap<>();
        }
    }
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setObject(i, parameter.getCode());
    }
    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String s = rs.getString(columnName);
        return s == null ? null : map.get(s);
    }
    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String s = rs.getString(columnIndex);
        return s == null ? null : map.get(s);
    }
    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String s = cs.getString(columnIndex);
        return s == null ? null : map.get(s);
    }
}