發現問題
今天在除錯系統錯誤通知的時候遇到了一個問題。我們在系統異常時候要通過佇列系統傳送各種通知到團隊內部成員。
因此我寫了一個通用介面。介面中有傳遞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。至此問題解決。