优雅地实现java枚举与int值地转换(Spring表单提交、Jackson、Mybatis、dubbo)

阿里巴巴的开发规范里有这么一条:

5.【强制】二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值里不允许使用枚举类型或者包含枚举类型的POJO对象。

网上很多人将此粗暴的理解为不让用枚举,这是错误的。
这个规范只是不让在接口返回值中使用枚举,因为会存在潜在的序列化问题。但是在其他地方是可以使用枚举的,毕竟大家都用int的话,维护起来简直就是地狱。

0、目标

假如我有一个POJO类,里面有一个Color枚举:

public class Fruit{
    public Long id;
    public Color color;
}

@Getter
@AllArgsConstructor
public enum Color extends ConvertibleEnum{
    RED(0,"red"),
    GREEN(0,"green");
    private final value;
    private final description;
}

我们的目标就是像上面的代码一样,代码中无需做任何的处理(仅仅让枚举类继承一个接口),在接收前端数据、将数据返回给前端、将数据存储到数据库中时,我的Color枚举都可以自动和int转换,这样既方便我们的开发,又不违反阿里巴巴的开发规范

一、创建接口

只要创建如下的接口,然后让自己的枚举类实现这个接口即可

public interface ConvertibleEnum{
    Integer getValue();
}

二、适配Jackson

Jackson的处理比较简单,直接在枚举中加上@JsonValue注解即可

import com.fasterxml.jackson.annotation.JsonValue;

@Getter//这个是lombok的注解,可以生成getter,或者自己加上getter
@AllArgsConstructor
public enum MyStatus implements ConvertibleEnum{//记得实现第一步中定义的接口
    CREATE(1,"已创建"),
    DELETED(1,"已删除");
    @JsonValue//加上这个注解即可,对于枚举类型,jackson在序列化和反序列化的时候都会使用这个值作为转换的依据
    private final Integer value;
    private final String title;
}

三、适配Spring的表单提交

3.1 创建converter factory

public class SpringFormEnumConverterFactory implements ConverterFactory<String, ConvertibleEnum> {
    @Override
    public <T extends ConvertibleEnum> Converter<String, T> getConverter(Class<T> targetType) {
        return new ValuedEnumConverter<>(targetType);
    }

    private class ValuedEnumConverter<T extends ConvertibleEnum> implements Converter<String, T> {

        private final T[] values;

        public ValuedEnumConverter(Class<T> targetType) {
            values = targetType.getEnumConstants();
        }

        @Override
        public T convert(String source) {
            if (source == null || source.isEmpty())
                return null;
            if (source.matches("[0-9]+")) {//支持数字->枚举转换
                for (T t : values) {
                    if (t.getValue().equals(Integer.valueOf(source))) {
                        return t;
                    }
                }
            } else {
                for (T value : values) {//支持原来默认的字符串->枚举转换
                    if (value.toString().equals(source))
                        return value;
                }
            }
            return null;
        }
    }
}

3.2 注册bean


@Component
public class MyBeansConfig {
    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
    @PostConstruct
    public void addConversionConfig() {
        ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) requestMappingHandlerAdapter
                .getWebBindingInitializer();
        if (initializer != null && initializer.getConversionService() != null) {
            GenericConversionService genericConversionService = (GenericConversionService) initializer.getConversionService();
            //genericConversionService.addConverter(new StringToDateConverter());
            genericConversionService.addConverterFactory(new SpringFormEnumConverterFactory());
        }
    }
}

然后在表单提交的时候就可以直接用数字类型来提交了

四、 适配Mybatis


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;

public class ValuedEnumTypeHandler<E extends ConvertibleEnum> extends BaseTypeHandler<E> {
    private final Class<E> type;
    private final E[] enums;

    public ValuedEnumTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
        if (type.isEnum()) {
            this.enums = type.getEnumConstants();
        } else {
            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.getValue());
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int i = rs.getInt(columnName);
        return convertFromInt(i, rs.wasNull());//注意这个wasNull是返回的上一个使用rs.get*()获取的记录是否为空
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int i = rs.getInt(columnIndex);
        return convertFromInt(i, rs.wasNull());
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int i = cs.getInt(columnIndex);
        return convertFromInt(i, cs.wasNull());
    }

    private E convertFromInt(int value, boolean isNull) {
        if (isNull)
            return null;
        for (E e : enums) {
            if (e.getValue().equals(value))
                return e;
        }
        //这里遇到转换不了的直接抛出异常了,你也可以选择返回null值,看需求
        throw new IllegalArgumentException(String.format("不支持将值%d转换为枚举%s!", value, type.getSimpleName()));
    }
}

然后再在配置文件中加上如下配置:

# 这里我用的yaml配置,其他方式也大同小异
mybatis:
  configuration:
    # 指定枚举类型在数据库读写时的handler
    default-enum-type-handler: com.your.module.ValuedEnumTypeHandler

之后在存取数据库的时候既可以把枚举类型和数据库中的int自由转换了

五、适配dubbo

适配dubbo的话,我看官方的文档应该要自己实现一个扩展点,具体的文档在这里:
http://dubbo.apache.org/zh-cn/docs/dev/impls/serialize.html,
目前我还没有遇到在RPC中传递枚举的状况,等以后填坑吧

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页