JSON字符串反序列化 动态泛型
需求:定时任务扫描,反射调用目标对象,但是,方法的传参不是固定的。
方案一:将方法参数存成JSON字符串,然后JSON反序列化成对象,然后反射调用
目标方法时这样的:
CommandResp sendXXX(BaseCommandApiDTO<XXX> baseCommandApiDTO);
方式一:FastJson
Class mainBody = Class.forName(entity.getMainBodyType());
ParameterizedTypeImpl parameterizedType = new ParameterizedTypeImpl(new Type[]{mainBody}, null, BaseCommandApiDTO.class);
Object obj = JSON.parseObject(entity.getMsgText(), parameterizedType);
CommandResp resp = ReflectUtil.invoke(serviceObj, methodName, obj);
方式二:Jackson
public class ObjectMapperHolder {
private static final ObjectMapper objectMapper = new ObjectMapper();
public static ObjectMapper getObjectMapper() {
objectMapper.registerModule(new Jdk8Module());
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
}
ObjectMapper mapper = ObjectMapperHolder.getObjectMapper();
JavaType javaType = mapper.getTypeFactory().constructParametricType(BaseCommandApiDTO.class, mainBody);
Object obj = mapper.readValue(entity.getMsgText(), javaType);
CommandResp resp = ReflectUtil.invoke(serviceObj, methodName, obj);
实践中发现,这两种方式容易导致OOM
方案二:直接将参数对象存到数据库中
数据库对应字段设置BLOB类型(这里设置的是MEDIUMBLOB) ,对应的java字段类型是byte[]
直接将Java对象序列化为二进制数据,这种方式的存储和读取速度非常快,因为序列化和反序列化过程不需要转换格式。
// 写入对象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(baseCommandApiDTO);
oos.flush();
byte[] data = bos.toByteArray();
// 读取对象
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(entity.getMsgObj()));
Object obj = ois.readObject();
最后的最后,优化建议:
1、尽量不要在数据库中存json字符串,如果非要存,建议字段类型设置为json,这样可以节省空间。因为你无法控制json字符串的长度,所以长度设置是个问题,另外json反序列化比较占内存,想想从一个对象变成两个对象当然要占用更多内存。
2、长度很大的字段(比如blob类型的)建议单独存一张关联表
补充知识:
1、一个对象序列化成JSON字符串后,这个字符串会比原对象占用更多的内存。
在序列化过程中,对象会被转换为字符串形式存储在堆内存中。由于字符串的存储方式与对象不同,通常会导致内存占用增加。具体来说,未序列化时,对象存储在堆内存中,而序列化后,这些对象被转换为字符串,字符串也需要额外的存储空间。此外,序列化过程中可能会涉及到更多的计算和内存分配,进一步增加了资源消耗。
序列化对内存占用的具体影响:
- 存储空间增加:序列化后的字符串通常会比原对象占用更多的存储空间,因为字符串需要更多的字节来存储。例如,JSON格式的序列化会包含很多结构化的符号和字符,这些都会增加存储需求。
- 计算资源消耗:序列化过程需要计算对象的属性和值,并将其转换为字符串形式。这个过程可能会消耗更多的CPU资源,尤其是在处理复杂对象时。
- 内存占用变化:序列化后,对象被存储为字符串,如果对象较大或数量较多,内存占用可能会显著增加。例如,一个包含大量数据的集合在序列化后,其占用的内存可能是未序列化时的两倍或更多。
2、日志输出不当也有可能造成内存溢出
比如:log.info("请求参数: {}", JSON.toJSONString(DTO))
即使日志级别是ERROR,但是当代码执行到这一行时,仍然会执行JSON.toJSONString(DTO)这句,而将对象序列化成json字符串的这个动作会消耗一定的内存,尤其是当请求量大,而且打印的对象也很多的时候就有可能成为压垮JVM的一根稻草。
在这里log.info()起到的作用是决定要不要把日志输出到控制台或文件。
3、对于大数据量,可以使用流式解析,逐行读取和处理JSON数据,避免一次性将整个JSON加载到内存中。
参考博客 《对象序列化内存占用问题》
https://www.cnblogs.com/Nuwa/p/18027475