netty系列之:netty中的核心解碼器json

簡介

程式和程式之間的數據傳輸方式有很多,可以通過二進位協議來傳輸,比較流行的像是thrift協議或者google的protobuf。這些二進位協議可以實現數據的有效傳輸,並且通過二進位的形式可以節省數據的體積,在某些速度和效率優先的情況下是非常有效的。並且如果不同的程式語言之間的相互調用,也可以通過這種二進位的協議來實現。

雖然二進位更加快速和有效,但是對於程式設計師來說不是很友好,因為一個人很難直接讀取二進位文件,雖然也存在一些一些文本的數據傳輸方式,比如XML,但是XML的繁瑣的標籤導致了XML在使用中有諸多的不便。於是一種通用的文本文件傳輸格式json誕生了。

能讀到這篇文章的朋友肯定對json不陌生了,當然還有一些更加簡潔的文件格式,比如YAML,感興趣的朋友可以更深入的了解一下。

這裡我們想要講的是netty對json的解碼。

java中對json的支援

在java中我們json的使用通常是將一個對象轉換成為json進行數據傳輸,或者將接收到json進行解析,將其轉換成為對象。

可惜的是在JDK中並沒有提供給一個好用的JSON工具,所以我們一般需要藉助第三方的JSON包來實現Object和JSON之間的轉換工作。

通常使用的有google的GSON,阿里的FastJSON和jackson等。

這裡我們使用google的GSON來進行介紹。

這裡我們主要講解的是java中對象和json的互相轉換,所以GSON中其他更加強大的功能這裡就不介紹了。

首先我們創建一個JAVA對象,我們定義一個Student類,如下所示:

    static class Student {
        String name;
        String phone;
        Integer age;

        public Student(String name, String phone, Integer age) {
            this.name = name;
            this.phone = phone;
            this.age = age;
        }
    }

這個類中,我們為Student定義了幾個不同的屬性和一個構造函數。接下來我們看下如何使用GSON來對這個對象進行JSON的轉換:

        Student obj = new Student("tina","188888888",18);
        Gson gson = new Gson();
        String json = gson.toJson(obj);
        System.out.println(json);
        Student obj2 = gson.fromJson(json, Student.class);
        System.out.println(obj2);

GSON使用起來非常簡單,我們構建好Gson對象之後,直接調用它的toJson方法即可將對象轉換成為json字元串。

然後調用json的fromJson方法就可以將json字元串轉換成為對象。

上面的程式碼輸出如下:

{"name":"tina","phone":"188888888","age":18}
com.flydean.JsonTest$Student@4534b60d

netty對json的解碼

netty為json提供了一個解碼器叫做JsonObjectDecoder,先來看下JsonObjectDecoder的定義:

public class JsonObjectDecoder extends ByteToMessageDecoder

和前面講解的base64,byte數組不同的是,JsonObjectDecoder繼承的是ByteToMessageDecoder而不是MessageToMessageDecoder。

這說明JsonObjectDecoder是直接從ByteBuf轉換成為Json Object對象。

我們知道JDK中並沒有JSON這個對象,所有的對象都是從第三方包中引入的,netty並沒有引入新的對象,所以netty中從Json中解析出來的對象還是一個ByteBuf對象,在這個ByteBuf中包含了一個Json對象。

JsonObjectDecoder的解析邏輯是怎麼樣的呢?

首先來看下JsonObjectDecoder中定義的4個state:

    private static final int ST_CORRUPTED = -1;
    private static final int ST_INIT = 0;
    private static final int ST_DECODING_NORMAL = 1;
    private static final int ST_DECODING_ARRAY_STREAM = 2;

ST_INIT表示的是decode的初始狀態,ST_CORRUPTED表示的是decode中出現的異常狀態。

ST_DECODING_NORMAL代表的是一個普通的json,如下所示:

{
	"source": "web",
	"type": "product_info",
	"time": 1641967014440,
	"data": {
		"id": 30000084318055,
		"staging": false
	},
	"dataId": "123456"
}

ST_DECODING_ARRAY_STREAM代表的是一個數組,對於數組來說,數組也是一個對象,所以數組也可以用json表示,下面就是一個常見的json數組:

[ "Google", "Runoob", "Taobao" ]

JsonObjectDecoder的解碼邏輯比較簡單,它主要是讀取ByteBuf中的數據,通過判斷讀取的數據和json中特有的大括弧,中括弧,逗號等分隔符來分割和解析json對象。

要注意的是,JsonObjectDecoder要解碼的ByteBuf中的消息應該是UTF-8編碼格式的,為什麼需要UTF-8格式呢?

這是因為json中那些特有的分隔符,即使在UTF-8中也是用一個byte來存儲的,這樣我們在讀取數據的過程中,可以通過讀取的byte值和json的分隔符進行比較,從而來確定json中不同對象的界限。

如果換成其他的編碼方式,json中的分隔符可能會用多個byte來表示,這樣對我們的解析就提高了難度,因為我們需要知道什麼時候是分隔符的開始,什麼時候是分隔符的結束。

它的核心解碼邏輯如下,首先從ByteBuf中讀取一個byte:

byte c = in.getByte(idx);

然後通過調用decodeByte(c, in, idx); 來判斷當前的位置是開括弧,還是閉括弧,是在一個對象的字元串中,還是一個新的對象字元串。

首先需要對當前的state做一個判斷,state判斷調用的是initDecoding方法:

    private void initDecoding(byte openingBrace) {
        openBraces = 1;
        if (openingBrace == '[' && streamArrayElements) {
            state = ST_DECODING_ARRAY_STREAM;
        } else {
            state = ST_DECODING_NORMAL;
        }
    }

接著就是對當前的state和自定義的4個狀態進行比較,如果是普通的json對象,並且對象已經是閉括弧狀態,說明該對象已經讀取完成,可以將其進行轉換並輸出了:

 if (state == ST_DECODING_NORMAL) {
                decodeByte(c, in, idx);
                if (openBraces == 0) {
                    ByteBuf json = extractObject(ctx, in, in.readerIndex(), idx + 1 - in.readerIndex());
                    if (json != null) {
                        out.add(json);
                    }
    ...

如果state表示目前是一個數組對象,數組對象中可能包含多個對象,這些對象是通過逗號來區分的。逗號之間還可能會有空格,所以需要對這些數據進行特殊判斷和處理,如下所示:

else if (state == ST_DECODING_ARRAY_STREAM) {
                decodeByte(c, in, idx);

                if (!insideString && (openBraces == 1 && c == ',' || openBraces == 0 && c == ']')) {
                    for (int i = in.readerIndex(); Character.isWhitespace(in.getByte(i)); i++) {
                        in.skipBytes(1);
                    }
                    int idxNoSpaces = idx - 1;
                    while (idxNoSpaces >= in.readerIndex() && Character.isWhitespace(in.getByte(idxNoSpaces))) {
                        idxNoSpaces--;
                    }
                    ByteBuf json = extractObject(ctx, in, in.readerIndex(), idxNoSpaces + 1 - in.readerIndex());
                    if (json != null) {
                        out.add(json);
                    }
    ....

最後將解析出來的json對象放入byteBuf的out list中,整個解析到此結束。

總結

以上就是netty中json核心解碼器JsonObjectDecoder的使用,它的本質是通過判斷json對象中的分割符來分割多個json字元串,然後將分割後的json字元串存入ByteBuf中輸出。

看到這裡,大家可能會疑惑了,decoder不是和encoder一起出現的嗎?為什麼netty中只有JsonObjectDecoder,而沒有JsonObjectEncoder呢?

事實上,這裡的Json對象就是一個包含Json字元的字元串,這個字元串被寫入到ByteBuf中,所以這裡並不需要特殊的encoder。

本文已收錄於 //www.flydean.com/14-3-netty-codec-json/

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

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