簡介
程式和程式之間的資料傳輸方式有很多,可以通過二進位制協議來傳輸,比較流行的像是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。
本文已收錄於 http://www.flydean.com/14-3-netty-codec-json/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!