netty-socketio event事件接收参数时json字符串转java bean报错

socketio依赖及版本

关键代码

Message javaBean

@Data
public class MyMessage {
    private String username;
    private Integer age;
    private String content;
}

server端 代码片段

// 监听事件
@OnEvent("message")
public void onMessage(SocketIOClient client, MyMessage message) {
    log.info("接收消息: sessionId={}, message={}", client.getSessionId(), message.toString());
}

client-js端 代码片段

var msg = {
    username: "icetea",
    age: 18,
    content: "my name is icetea."
};
socket.emit('message', msg);

client-java端 代码片段

MyMessage msg = new MyMessage("icetea", 18, "my name is icetea.");
socket.emit("message", msg); // 直接传javaBean不行
// 或者 socket.emit("message", JSON.toJSONString(msg)); javaBean转为json string也不行

问题描述

使用socketio js客户端时可以正常解析参数,但使用 socketio java客户端发送java bean消息时,netty-socketio无法解析传来的参数,并且将javabean转换成json字符串也会报错

报错详情

ERROR 11236 — [ntLoopGroup-3-3] c.c.socketio.JsonSupportWrapper: Can’t read value: [“message-model”,“MyMessage(username=icetea, age=18, content=my name is icetea.)”] for type: class com.corundumstudio.socketio.protocol.Event

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of life.icetea.test.nettysocketio.domain.MyMessage (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value (‘MyMessage(username=icetea, age=19, content=my name is icetea.)’)
at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 18]

问题解析

查看netty-socketio官方仓库的issues发现有相同的问题,https://github.com/mrniko/netty-socketio/issues/258, 里面有人说是client传参序列化的问题,并且给出了解决方案。如下:

参考1:https://github.com/mrniko/netty-socketio/issues/258#issuecomment-417607871

//Android client use json parser is "org.json",when emit message,you should like this:
ChatEvent data = new ChatEvent(); 
mSocket.emit("message", new JSONObject(data));

// 该方法中的org.json的JSONObject构造只能传递一个map类型

参考2:https://github.com/mrniko/netty-socketio/issues/258#issuecomment-449638424

//try and convert the object you are trying to send to a string using GSON library
mSocket.emit("message", new JSONObject(new Gson().toJson(data)));

// 该方法使用Gson所以需要引入Gson依赖

参考3: https://github.com/mrniko/netty-socketio/issues/258#issuecomment-465497609

此回答解释了为什么js-client会成功而java-client会失败的原因,因为java-client在序列化参数时存在问题。

参考后得出的解决方法

参考前两个回答,都是将javaBean转成对应依赖(Gson和org.json)的Json对象,因为我的项目使用的是fastJson,而fastJson也有对应的JSONObject,所以照葫芦画瓢,示例如下:

MyMessage msg = new MyMessage("icetea", 18, "my name is icetea.");
socket.emit("message", JSON.toJSON(msg));
// 该方法能够让server正常接收参数

总结

找到解决方法,然后来详细总结一下为什么在socket.emit()方法中直接传递javabean或者传递json的字符串不行,debug找到socketio-java-client底层序列化参数的源码片段如下:

// io.socket.client.Socket#emit(java.lang.String, java.lang.Object[], io.socket.client.Ack)
// ······
// 要发送的参数都放到了org.json.JSONArray对象
JSONArray jsonArgs = new JSONArray();
jsonArgs.put(event);
if (args != null) {
    for (Object arg : args) {
        jsonArgs.put(arg);
    }
}
// 将JSONArray参数封装成packet
Packet<JSONArray> packet = new Packet<JSONArray>(Parser.EVENT, jsonArgs);
// ······

从以上片段看出要发送的参数都放到了org.json.JSONArray对象中并封装成Packet,然后再往下debug找到了将参数encode成String的方法,代码片段如下:

// io.socket.parser.IOParser.Encoder#encodeAsString
// ······
private String encodeAsString(Packet obj) {
    StringBuilder str = new StringBuilder("" + obj.type);
    // ······
    if (obj.data != null) {
        str.append(obj.data);
    }
    // ······
    return str.toString();
}
// 

从以上片段看出,Packet有被解析成一串字符串,且要传送的参数是字符串中的一部分,然后往下debug就是直接send方法。到此参数封装结束。那么从以上分析得出,java-client的参数封装和JSONArray这个类有很大关系,所以可以测试一下JSONArray的toString方法看看展示出的结果是什么样子:

public static void main(String[] args) {
    MyMessage msg = new MyMessage("icetea", 18, "my name is icetea.");

    // 直接put java bean
    JSONArray jsonArray = new JSONArray();
    jsonArray.put("message");
    jsonArray.put(msg);
    System.out.println(jsonArray);
    
    // 转成fastJson的JSONObject
    JSONArray jsonArray1 = new JSONArray();
    jsonArray1.put("message");
    jsonArray1.put(JSON.toJSON(msg));
    System.out.println(jsonArray1);

    // 使用Map测试一下,因为fastJson的JSONObject其实就是一个Map
    JSONArray jsonArray2 = new JSONArray();
    jsonArray2.put("message");
    HashMap<String, Object> map = new HashMap<>();
    map.put("username", "icetea");
    map.put("age", 18);
    map.put("content", "my name is icetea.");
    jsonArray2.put(map);
    System.out.println(jsonArray2);

    // 将javaBean转成json string测试
    JSONArray jsonArray3 = new JSONArray();
    jsonArray3.put("message");
    jsonArray3.put(JSON.toJSONString(msg));
    System.out.println(jsonArray3);
}

// 输出如下:
["message","MyMessage(username=icetea, age=18, content=my name is icetea.)"]
["message",{"age":18,"content":"my name is icetea.","username":"icetea"}]
["message",{"age":18,"content":"my name is icetea.","username":"icetea"}]
["message","{\"age\":18,\"content\":\"my name is icetea.\",\"username\":\"icetea\"}"]

从测试结果可以看出,第二种方式(转成JSONObject)和第三种方式(使用Map封装参数)得出的结果符合json字符串的标准,且能够被netty-socketio反序列化成javaBean。

总结的出另一种方式
// 注意:使用map方式属性必须都是基本类型,不能嵌套有javaBean,否则还会报错,所以推荐使用JSONObject方式
Map<String, Object> msg = new HashMap<>();
msg.put("username", "icetea");
msg.put("age", 18);
msg.put("content", "my name is icetea.");
socket.emit("message", msg);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值