Spring Boot 枚举类型的自动转换

2023-11-17 16:48:49 浏览数 (1)

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

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

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

枚举父类

代码语言:c#复制
/**
 * @author rookie
 */
public interface IEnum<T extends Serializable> {

    /**
     * 获取值
     * @return 值
     */
    T getValue();
}
1.1、请求头接收参数

添加Convert

代码语言:typescript复制
@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

代码语言:typescript复制
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private EnumConvertFactory enumConvertFactory;

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

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

代码语言:less复制
@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 默认是根据下标进行转换的,和我们根据匹配值获取相应枚举不符,所以进行以下更改:

添加枚举反序列化处理器

代码语言:scala复制
@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;
        }
    }
}

注册处理器

代码语言:java复制
@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 序列化默认是按照名称序列化的,和我们想返回枚举中的某个值不符,下面我们进行一下小的改动:

添加序列化处理器

代码语言:scala复制
public class IEnumSerializer extends JsonSerializer<IEnum> {
    @Override
    public void serialize(IEnum iEnum, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(iEnum.getName());
    }
}

注册序列化处理器

代码语言:java复制
@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);
    }
}

添加自定义枚举处理器:

代码语言:java复制
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) 的枚举处理器

代码语言:text复制
mybatis-plus:
  configuration:
    default-enum-type-handler: XXX.config.IEnumOrdinalTypeHandler

好了这样就行了。

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

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞