关于在搭建微服务项目中使用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);
}