netty系列之:netty中的核心解碼器json
- 2022 年 4 月 26 日
- 筆記
- JAVA, netty, java-io-nio, 程式那些事
簡介
程式和程式之間的數據傳輸方式有很多,可以通過二進位協議來傳輸,比較流行的像是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/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

