Spring Boot 枚举类型的自动转换

1、Spring Boot 枚举类型的自动转换

需求:一般我们在数据库都会定义数值型的枚举常量,不管是序列化还是反序列化都是需要我们手动去转换成枚举类型的,既然这样我们能不能让它们自动转换呢?接下来我们就来尝试一下:

首先解决如何接收枚举类型。

枚举父类

/**
 * @author rookie
 */
public interface IEnum<T extends Serializable> {

    /**
     * 获取值
     * @return 值
     */
    T getValue();
}

1.1、请求头接收参数

添加Convert

@Component
public class EnumConvertFactory implements ConverterFactory<String, IEnum<?>> {

    @Override
    public <T extends IEnum<?>> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToEnum<>(targetType);
    }


    public static class StringToEnum<T extends IEnum<?>> implements Converter<String, T> {

        private final Class<T> targetType;

        public StringToEnum(Class<T> targetType) {
            this.targetType = targetType;
        }

        @Override
        public T convert(String source) {
            if (!StringUtils.hasText(source)) {
                return null;
            }
            return (T) EnumConvertFactory.getEnum(this.targetType, source);
        }
    }

    public static <T extends IEnum<?>> T getEnum(Class<T> targetType, String source) {
        for (T constant : targetType.getEnumConstants()) {
            if (source.equals(String.valueOf(constant.getValue()))) {
                return constant;
            }
        }
        return null;
    }
}

注册Convert

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private EnumConvertFactory enumConvertFactory;

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

我们只要实现 IEnum ,然后在我们的接收实体类中定义相应的枚举类型就能自动转换成枚举类型了,比如这样:

@Getter
@AllArgsConstructor
public enum TestEnum implements IEnum<String>{

    /**
     * 测试
     */
    TEST_ENUM("1","2");
    
    private final String value;

    private final String msg;

}
1.2、请求体接收

Jackson接收枚举

如果我们接收的是 JSON 字符串类型,那么 Jackson 默认是根据下标进行转换的,和我们根据匹配值获取相应枚举不符,所以进行以下更改:

添加枚举反序列化处理器

@Data
@EqualsAndHashCode(callSuper = true)
public class EnumDeserializer extends JsonDeserializer<Enum<?>> implements ContextualDeserializer {

    private Class<?> target;

    @SuppressWarnings("all")
    @Override
    public Enum<?> deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
        if (!StringUtils.hasText(jsonParser.getText())) {
            return null;
        }
        if (IEnum.class.isAssignableFrom(target)) {
            return (Enum<?>) EnumConvertFactory.getEnum((Class) target, jsonParser.getText());
        }
        return defaultEnumTransform(target,jsonParser.getText());
    }

    /**
     * @param ctx      ctx
     * @param property property
     * @return 1
     * @throws JsonMappingException
     */
    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctx, BeanProperty property) throws JsonMappingException {
        Class<?> rawCls = ctx.getContextualType().getRawClass();
        EnumDeserializer enumDeserializer = new EnumDeserializer();
        enumDeserializer.setTarget(rawCls);
        return enumDeserializer;
    }


    public static Enum<?> defaultEnumTransform(Class<?> type, String indexString) {
        Enum<?>[] enumConstants = (Enum<?>[]) type.getEnumConstants();
        try {
            int index = Integer.parseInt(indexString);
            return enumConstants[index];
        } catch (NumberFormatException e) {
            return null;
        }
    }
}

注册处理器

@Component
public class JacksonConfig implements SmartInitializingSingleton {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void afterSingletonsInstantiated() {
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addDeserializer(Enum.class, new EnumDeserializer());
        objectMapper.registerModule(simpleModule);
    }	
}

使用方法和上面一致。

1.3、添加自定义枚举序列化

接下来我们就要解决如何将数据库中的数值常量枚举转换成

jackson 序列化默认是按照名称序列化的,和我们想返回枚举中的某个值不符,下面我们进行一下小的改动:

添加序列化处理器

public class IEnumSerializer extends JsonSerializer<IEnum> {
    @Override
    public void serialize(IEnum iEnum, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(iEnum.getName());
    }
}

注册序列化处理器

@Component
public class BeanLoadProcess implements SmartInitializingSingleton {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void afterSingletonsInstantiated() {
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(IEnum.class,new IEnumSerializer());
        objectMapper.registerModule(simpleModule);
    }
}

添加自定义枚举处理器:

import com.galaxy.sentry.alarm.constant.enums.IEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 重写枚举处理器,数据库中只能存 code 类型是 int 类型的,其他类型转换会报错
 * 当然可以自己扩展以下的处理,判断相应的枚举类型然后进行相应处理
 * @author rookie
 */
public class IEnumOrdinalTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
    private final Class<E> type;
    private final E[] enums;

    public IEnumOrdinalTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        } else {
            this.type = type;
            this.enums = (E[]) type.getEnumConstants();
            if (this.enums == null) {
                throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
            }
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.ordinal());
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int ordinal = rs.getInt(columnName);
        return ordinal == 0 && rs.wasNull() ? null : this.toOrdinalEnum(ordinal);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int ordinal = rs.getInt(columnIndex);
        return ordinal == 0 && rs.wasNull() ? null : this.toOrdinalEnum(ordinal);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int ordinal = cs.getInt(columnIndex);
        return ordinal == 0 && cs.wasNull() ? null : this.toOrdinalEnum(ordinal);
    }

    private E toOrdinalEnum(int ordinal) {
        try {
            if (IEnum.class.isAssignableFrom(type)) {
                for (E constant : type.getEnumConstants()) {
                    if (ordinal == ((IEnum<Integer>) constant).getCode()) {
                        return constant;
                    }
                }
                return null;
            }
            return this.enums[ordinal];
        } catch (Exception var3) {
            throw new IllegalArgumentException("Cannot convert " + ordinal + " to " + this.type.getSimpleName() + " by ordinal value.", var3);
        }
    }
}

因为我们先一步是让数据库中的常量能转换成枚举类型,这里我们定义一下 Mybatis plus(我用的是plus) 的枚举处理器

mybatis-plus:
  configuration:
    default-enum-type-handler: XXX.config.IEnumOrdinalTypeHandler

好了这样就行了。

更多内容请关注公众号或者官方网站。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值