1.问题
通过http调用请求,传输的是一个对象列表,其中列表中的对象含有枚举,
发现一直无法解析成功,并抛出以下错误,很明显是枚举无法解析成功;
POST https://xxx/audit/api/v1/asyncSaveAuditLog
Content-Type: application/json;charset=utf-8
[
{"sysName":"SEEWO_USERCENTER"}
]
{
"timestamp": 1627466213248,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "JSON parse error: Can not construct instance of java.lang.Enum: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.lang.Enum: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: java.io.PushbackInputStream@4ceb6da5; line: 2, column: 14] (through reference chain: java.util.ArrayList[0]->com.seewo.audit.api.dto.SaveAuditLogQuery[\"sysName\"])",
"path": "/audit/api/v1/asyncSaveAuditLog"
}
2.思路
1.0.暴力破解
使用@RequestBody String body来接收对象,然后再使用JSON.parseArray()解析成JSONObject,在对特殊的枚举特殊处理;但太弱智了;
1.1.Converter
后来发现Converter仅用于点对点的转换,不适用于一整个列表对象的转换,因为这样就需要你来完成json解析的工作了,因此这种方式不行;
1.2. MessageConverter
Springboot提供的一个扩展工具接口,用于解析http中@RequestBody的消息,但后来发现,就是因为原有的MessageConverter处理不了才抛出这个异常的,要想解决这个问题只能添加新的MessageConverter,手写吧,工作量较大,而且还需要测试;
后来想到可以引用FastJsonHttpMessageConverter,但发现它解析不了列表,因为我传输的是一个列表;因此这种方式不行;
1.3.@JSONField
后来了解到fastjson提供了该注解用于自定义解析规则,因此使用该注解,问题解决;
@JSONField(deserializeUsing = EnumTypeDeserializer.class)
private Enum<AuditServiceEnum> sysName;
package com.seewo.audit.api.utils;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
public class EnumTypeDeserializer implements ObjectDeserializer {
private static final Logger logger = LoggerFactory.getLogger(EnumTypeDeserializer.class);
private static final String prefixEnum = "java.lang.Enum<";
@Override
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
//[{"sysName":"SEEWO_USERCENTER"}]
//其中sysName为fielName,SEEWO_USERCENTER为value
Object value = parser.parse();
String typeName = type.getTypeName();
//如果是 java.lang.Enum< 开头,说明是 java.lang.Enum<com.seewo.audit.api.enums.AuditServiceEnum> 这种形式,因此需要去除前缀
if (typeName.startsWith(prefixEnum)) {
typeName = typeName.substring(prefixEnum.length(), typeName.length() - 1);
}
//直接反射
try {
Class<?> clazz = Class.forName(typeName);
Method method = clazz.getMethod("valueOf", String.class);
Object ret = method.invoke(null, value);
return (T) ret;
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
logger.error("反序列化枚举失败:{}, {}", fieldName, e.getMessage());
}
return null;
}
@Override
public int getFastMatchToken() {
return 0;
}
}
3.反思
解决这个问题花了将近4小时,哎,我还是太菜了,仍需继续努力;