jackson对Exception类型对象的序列化与反序列化
发现问题
今天在调试系统错误通知的时候遇到了一个问题。我们在系统异常时候要通过队列系统发送各种通知到团队内部成员。
因此我写了一个通用接口。接口中有传递Exception对象到队列中,再由队列消费者解析后生成消息发送出去。
这个Exception对象是先通过jackson序列化后再在队列消费端反序列化完成传递的。
但是在测试过程中发现,经过队列传递后的Exception对象丢失了很多信息,甚至连基本的class类型都不能还原。
比如一个IllegalArgumentException经过jackson的序列化与反序列化之后得到的是一个几乎空的Exception对象。从message到cause到stackTrace数据全部丢失。
例如:
IllegalStateException e = new IllegalStateException("abc");
String str = JSONSnakeUtils.writeValue(e);
Exception ex = JSONSnakeUtils.readValue(str, Exception.class);
注:以上代码中JSONSnakeUtils是我们自己封装的jackson方法,基本原样调用了jackson的objectMapper方法。
上面方法中ex对象已经和原来的e对象天差地别了。
尝试解决
然后我想到了使用java自带的序列化工具来实现。
经过以下测试:
IllegalStateException e = new IllegalStateException("abc");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(byteArrayOutputStream);
oo.writeObject(e);
oo.close();
byte[] bytes = byteArrayOutputStream.toByteArray();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
Exception ex = (Exception) ois.readObject();
得到的ex对象原样还原了原始的e对象,说明这个思路是可行的。
最终方案
结合jackson自定义序列化与反序列化的方式,有了最终的解决方案:
首先定义自定义的序列化与反序列化方法,将经过ObjectOutputStream序列化后的byte数组转化为字符串通过json传递。再在消费端经过byte数组转换回Exception对象。代码如下:
定义序列化类
public class ExceptionJsonSerializer extends JsonSerializer<Exception> {
@Override
public void serialize(Exception exception, JsonGenerator gen, SerializerProvider serializers) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(byteArrayOutputStream);
oo.writeObject(exception);
oo.flush();
oo.close();
byte[] bytes = byteArrayOutputStream.toByteArray();
gen.writeRawValue("\"" + ByteArrayUtil.toHexString(bytes) + "\"");
}
}
定义反序列化类
public class ExceptionJsonDeserializer extends JsonDeserializer<Exception> {
@Override
public Exception deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String valueAsString = jsonParser.getValueAsString();
byte[] bytes = ByteArrayUtil.hexStringToByteArray(valueAsString);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
try {
Exception ex = (Exception) ois.readObject();
return ex;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
} finally {
ois.close();
}
}
}
最后在对象字段上添加注解
public static class AAA {
String xxx;
String yyy;
@JsonSerialize(using = ExceptionJsonSerializer.class)
@JsonDeserialize(using = ExceptionJsonDeserializer.class)
Exception exception;
}
OK。至此问题解决。