netty系列之:使用Jboss Marshalling來序列化java物件

flydean 發表於 2022-05-18
Java Netty

簡介

在JAVA程式中經常會用到序列化的場景,除了JDK自身提供的Serializable之外,還有一些第三方的產品可以實現對JAVA物件的序列化。其中比較有名的就是Google protobuf。當然,也有其他的比較出名的序列化工具,比如Kryo和JBoss Marshalling。

今天想給大家介紹的就是JBoss Marshalling,為什麼要介紹JBoss Marshalling呢?

用過google protobuf的朋友可能都知道,雖然protobuf好用,但是需要先定義序列化物件的結構才能生成對應的protobuf檔案。如果怕麻煩的朋友可能就不想考慮了。

JBoss Marshalling就是在JDK自帶的java.io.Serializable中進行優化的一個序列化工具,用起來非常的簡單,並且和java.io.Serializable相容,所以是居家必備開發程式的好幫手。

根據JBoss官方的介紹,JBoss Marshalling和JDK java.io.Serializable相比有兩個非常大的優點,第一個優點就是JBoss Marshalling解決了java.io.Serializable中使用的一些不便和問題。第二個優點就是JBoss Marshalling完全是可插拔的,這樣就提供了對JBoss Marshalling框架進行擴充套件的可能,那麼一起來看看JBoss Marshalling的使用吧。

新增JBoss Marshalling依賴

如果想用JBoss Marshalling,那麼第一步就是新增JBoss Marshalling的依賴。

很奇怪的是如果你到JBoss Marshalling的官網上,可能會發現JBoss Marshalling很久都沒有更新了,它的最新版本還是2011-04-27年出的1.3.0.CR9版本。

但是不要急,如果你去maven倉庫搜一下,會發現最新的版本是2021年5月發行的2.0.12.Final版本。

所以這裡我們就拿最新的2.0.12.Final版本為例進行講解。

如果仔細觀察JBoss Marshalling的maven倉庫,可以看到JBoss Marshalling包含了4個依賴包,分別是JBoss Marshalling API,JBoss Marshalling River Protocol,JBoss Marshalling Serial Protocol和JBoss Marshalling OSGi Bundle。

JBoss Marshalling API是我們在程式中需要呼叫的API介面,這個是必須要包含的。JBoss Marshalling River Protocol和JBoss Marshalling Serial Protocol是marshalling的兩種實現方式,可以根據需要自行取捨。

JBoss官網並沒有太多關於這兩個序列化實現的細節,我只能說,根據我的瞭解river的壓縮程度更高。其他更多細節和實現可能只有具體閱讀原始碼才知道了。

JBoss Marshalling OSGi Bundle是一個基於OSGi的可插拔的框架。

如果我們只是做物件的序列化,那麼只需要使用JBoss Marshalling API和JBoss Marshalling River Protocol就行了。

        <dependency>
            <groupId>org.jboss.marshalling</groupId>
            <artifactId>jboss-marshalling</artifactId>
            <version>2.0.12.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.marshalling</groupId>
            <artifactId>jboss-marshalling-river</artifactId>
            <version>2.0.12.Final</version>
        </dependency>

JBoss Marshalling的使用

新增了依賴之後,我們就可以開始使用JBoss Marshalling了。JBoss Marshalling的使用非常簡單,首先我們要根據選擇的marshalling方式建立MarshallerFactory:

 // 使用river作為marshalling的方式
        MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("river");

這裡我們選擇使用river作為marshalling的序列化方式。

有了MarshallerFactory,我們還需要一個MarshallingConfiguration為MarshallerFactory提供一些必要的序列化引數。

一般來說,我們可以控制MarshallingConfiguration的下面一些屬性:

MarshallingConfiguration configuration = new MarshallingConfiguration();
 configuration.setVersion(4);
 configuration.setClassCount(10);
 configuration.setBufferSize(8096);
 configuration.setInstanceCount(100);
 configuration.setExceptionListener(new MarshallingException());
 configuration.setClassResolver(new SimpleClassResolver(getClass().getClassLoader()));

setVersion是設定使用的marshalling protocol的版本號,這個版本號非常重要,因為依賴的protocol實現可能根據會根據需要進行序列化實現的升級,可能產生不相容的情況。通過設定版本號,可以保證升級之後的protocol也能相容之前的序列化版本。

setClassCount是預設要序列化物件中的class個數。

setInstanceCount是預設序列化物件中的class例項個數。

setBufferSize設定讀取資料的buff大小,通過調節這個屬性可以調整序列化的效能。

setExceptionListener新增序列化異常的時候的異常監聽器。

setClassResolver用來設定classloader。

JBoss Marshalling的強大之處在於我們在序列化的過程中還可以對物件進行攔截,從而進行日誌輸出或者其他的業務操作。

configuration提供了兩個方法,分別是setObjectPreResolver和setObjectResolver。

這兩個方法接受一個ObjectResolver物件,可以用來對物件進行處理。

兩個方法的不同在於執行的順序,preResolver在所有的resolver之前執行。

做好上面的配置之後,我們就可以正式開始編碼了。

            final Marshaller marshaller = marshallerFactory.createMarshaller(configuration);
            try(FileOutputStream os = new FileOutputStream(fileName)){
                marshaller.start(Marshalling.createByteOutput(os));
                marshaller.writeObject(obj);
                marshaller.finish();
            }

上面的例子中,通過呼叫marshaller的start方法開啟序列化,然後呼叫marshaller.writeObject寫入物件。

最後呼叫marshaller.finish結束序列化。

整個序列化的程式碼如下所示:

    public void marshallingWrite(String fileName, Object obj) throws IOException {
        // 使用river作為marshalling的方式
        MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("river");
        // 建立marshalling的配置
        MarshallingConfiguration configuration = new MarshallingConfiguration();
        // 使用版本號4
        configuration.setVersion(4);

            final Marshaller marshaller = marshallerFactory.createMarshaller(configuration);
            try(FileOutputStream os = new FileOutputStream(fileName)){
                marshaller.start(Marshalling.createByteOutput(os));
                marshaller.writeObject(obj);
                marshaller.finish();
            }
    }

    public static void main(String[] args) throws IOException {
        MarshallingWriter writer = new MarshallingWriter();
        Student student= new Student("jack", 18, "first grade");
        writer.marshallingWrite("/tmp/marshall.txt",student);
    }

非常的簡潔明瞭。

注意,這裡我們序列化了一個Student物件,這個物件一定要實現java.io.Serializable介面,否則會丟擲型別下面的異常:

Exception in thread "main" java.io.NotSerializableException: 
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:274)
    at org.jboss.marshalling.AbstractObjectOutput.writeObject(AbstractObjectOutput.java:58)
    at org.jboss.marshalling.AbstractMarshaller.writeObject(AbstractMarshaller.java:111)

接下來就是序列化的反向動作反序列化了。

程式碼很簡單,我們直接上程式碼:

   public void marshallingRead(String fileName) throws IOException, ClassNotFoundException {
        // 使用river協議建立MarshallerFactory
        MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("river");
        // 建立配置檔案
        MarshallingConfiguration configuration = new MarshallingConfiguration();
        // 使用版本號4
        configuration.setVersion(4);
            final Unmarshaller unmarshaller = marshallerFactory.createUnmarshaller(configuration);
            try(FileInputStream is = new FileInputStream(fileName)){
                unmarshaller.start(Marshalling.createByteInput(is));
                Student student=(Student)unmarshaller.readObject();
                log.info("student:{}",student);
                unmarshaller.finish();
            }
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        MarshallingReader reader= new MarshallingReader();
        reader.marshallingRead("/tmp/marshall.txt");
    }

執行上面的程式碼,我們可能得到下面的輸出:

[main] INFO  c.f.marshalling.MarshallingReader - student:Student(name=jack, age=18, className=first grade)

可見讀取序列化的物件已經成功。

總結

以上就是JBoss Marshalling的基本使用。通常對我們程式設計師來說,這個基本的使用已經足夠了。除非你有根據複雜的序列化需求,比如物件中的密碼需要在序列化的過程中進行替換,這種需求可以使用我們前面提到的ObjectResolver來實現。

本文的例子可以參考:learn-netty4

本文已收錄於 http://www.flydean.com/17-jboss-marshalling/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!