jackson對Exception型別物件的序列化與反序列化

Succour發表於2019-01-09

發現問題

今天在除錯系統錯誤通知的時候遇到了一個問題。我們在系統異常時候要通過佇列系統傳送各種通知到團隊內部成員。

因此我寫了一個通用介面。介面中有傳遞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。至此問題解決。

相關文章