关于在搭建微服务项目中使用openfeign作为rpc框架调用时,枚举对象无法进行转换问题

关于在搭建微服务项目中使用openfeign作为rpc框架调用时,枚举对象无法进行转换问题

我们在搭建微服务框架的时候, 一般都会使用openfeign作为服务间互相调用的rpc框架, 但是在使用的过程中发现, 当数据类型是枚举的话, openfeign在反序列化的时候就会报错. 前提是我们使用了jackjson的@JsonFormat(shape = JsonFormat.Shape.OBJECT)注解, 它是把注解转化为对象, 方便我们前端取出具体的内容, 就是因为这样, 导致反序列化的时候无法将枚举json对象转为枚举对象.

  • 例子:
@Getter
@JsonFormat(shape = JsonFormat.Shape.OBJECT) // 这个注解将枚举转为对象了
public enum IsOk {

    NO("0", "否"),
    OK("1", "是");

    private String message;

    @EnumValue
    @JSONField(serialize = false)
    private String code;


    IsOk(String code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getName() {
        return this.name();
    }
}
解决方案
  • 找到问题所在了,是jackjson 反序列化的原因,那我们就重新修改一下它的反序列化的逻辑, 代码如下:
    首先需要一些工具类:
public class ReflectUtil {

    /**
     * 判断是否是List或者ArrayList
     *
     * @param field
     * @return
     */
    public static boolean isList(Field field) {
        boolean flag = false;
        String simpleName = field.getType().getSimpleName();
        if (simpleName.contains("List")) {
            flag = true;
        }
        return flag;
    }

    /**
     * 判断是否是set
     *
     * @param field
     * @return
     */
    public static boolean isSet(Field field) {
        boolean flag = false;
        String simpleName = field.getType().getSimpleName();
        if (simpleName.contains("Set")) {
            flag = true;
        }
        return flag;
    }

    /**
     * 判断是否是Map或者HashMap
     *
     * @param field
     * @return
     */
    public static boolean isMap(Field field) {
        boolean flag = false;
        String simpleName = field.getType().getSimpleName();
        if ("Map".equals(simpleName) || "HashMap".equals(simpleName)) {
            flag = true;
        }
        return flag;
    }


    /**
     * 根据字段名查询对应字段的属性值。
     * @param parentCurrentName 字段名
     * @param clazz 对象
     * @return
     */
    public static Class getFieldType(String parentCurrentName, Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            String name = field.getName();
            Type genericType = field.getGenericType();
            if (parentCurrentName.equals(name)) {
                if (isList(field) || isSet(field)) {
                    if (genericType instanceof ParameterizedType) {
                        ParameterizedType parameterizedType = (ParameterizedType) genericType;
                        Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0];
                        return (Class) actualTypeArgument;
                    }
                } else if (field.getType().isArray()) {
                    return field.getType().getComponentType();

                } else if (isMap(field)) {
                    if (genericType instanceof ParameterizedType) {
                        ParameterizedType parameterizedType = (ParameterizedType) genericType;
                        Type actualTypeArgument = parameterizedType.getActualTypeArguments()[1];
                        return (Class) actualTypeArgument;
                    }
                } else {
                    return field.getType();
                }
            }
        }
        return null;
    }
}
  • 重写反序列化枚举的逻辑
public class BaseEnumDeserializer extends JsonDeserializer<Enum> {

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Enum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {

        JsonNode node = jp.getCodec().readTree(jp);
        String currentName = jp.currentName();
        Object currentValue = jp.getCurrentValue();
        Class findPropertyType;
        /*
          解释一下这里, currentName,currentValue,对象的属性是这样的 private IsOk isOk;
         */
        if (StringUtils.isNotEmpty(currentName) && StringUtils.isNotNull(currentValue)) {
            findPropertyType = BeanUtils.findPropertyType(currentName, currentValue.getClass());
        } else {
            /*
             * 上面的两个值为空时 对象的定义为集合时 private List<IsOk> isOk;
             * 我们使用这个api jp.getParsingContext().getParent().getCurrentName()
             */
            String parentCurrentName = jp.getParsingContext().getParent().getCurrentName();
            Object parentCurrentValue = jp.getParsingContext().getParent().getCurrentValue();
            /*
             * 下面使用反射的方法获取到具体属性值对应枚举值就是 private List<IsOk> isOk;中的IsOk 枚举
             */
            findPropertyType = ReflectUtil.getFieldType(parentCurrentName, parentCurrentValue.getClass());
        }
        Enum valueOf = null;
        if (StringUtils.isNotNull(findPropertyType)) {
            JsonFormat annotation = (JsonFormat) findPropertyType.getAnnotation(JsonFormat.class);
            /*
             * node.get("name") 的name 调用的就是枚举中的 
             * public String getName() {
             *   return this.name();
             * }
             * 所以就要求所有的枚举都必须添加getName()方法,否则就会报错
             */
            JsonNode name = node.get("name");
            if (annotation == null || annotation.shape() != Shape.OBJECT || name == null) {
                valueOf = Enum.valueOf(findPropertyType, node.asText());
            } else {
                valueOf = Enum.valueOf(findPropertyType, name.asText());
            }
        }
        return valueOf;
    }
}

具体的逻辑, 我在代码中添加了注释, 帮助理解, 这里还解决了当对象中的枚举是集合类型时的处理办法.可以查看代码中的注释

  • 将自己定义的逻辑注册到jackjson中, 方式有很多种,这里展示最简便的.
	@PostConstruct
    public void addDeserializer() {
        //注册转换器
        SimpleModule module = new SimpleModule();
        JsonDeserializer<Enum> deserialize = new BaseEnumDeserializer();
        module.addDeserializer(Enum.class, deserialize);
        // 就是从spring容器中获取对应的bean
        ObjectMapper bean = SpringUtils.getBean(ObjectMapper.class);
        bean.registerModule(module);
    }
完成上面的操作后, 就可以在调用的时候愉快的使用枚举类型了, 具体的实现逻辑可以自行打断点debug 进行理解.
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值