令人崩溃 的 Version4
V4 版修改了数据序列化的方式,不再是java原生的序列化,改用Json 格式来序列化数据,这里采用的是阿里的fastjson,引入maven,首先 fastjson 2.x的版本不完全兼容1.2.x版本,所以这时候还是引入源项目用的老版本吧(踩了坑)。据说1.2.80以下的版本有漏洞,这练习项目就无所谓了吧。
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.67</version>
</dependency>
首先新增一个序列化接口,之后不管用什么方式实现的序列化实现这个接口就行:
public interface Serializer {
// 把对象序列化成字节数组
byte[] serialize(Object obj);
// 从字节数组反序列化成消息, 使用java自带序列化方式不用messageType也能得到相应的对象(序列化字节数组里包含类信息)
// 其它方式需指定消息格式,再根据message转化成相应的对象
Object deserialize(byte[] bytes, int messageType);
// 返回使用的序列器,是哪个
// 0:java自带序列化方式, 1: json序列化方式
int getType();
// 根据序号取出序列化器,暂时有两种实现方式,需要其它方式,实现这个接口即可
static Serializer getSerializerByCode(int code){
switch (code){
case 0:
return new ObjectSerializer();
case 1:
return new JsonSerializer();
default:
return null;
}
}
}
编码类:
@AllArgsConstructor
public class MyEncode extends MessageToByteEncoder {
private Serializer serializer;
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
// 写入消息类型
if(msg instanceof RPCRequest){
out.writeShort(MessageType.REQUEST.getCode());
}
else if(msg instanceof RPCResponse){
out.writeShort(MessageType.RESPONSE.getCode());
}
/**
* 以这种消息格式发出
* [消息类型]:[序列化方式]:[数据长度] ------> [序列化数组]
*/
// 写入序列化方式
out.writeShort(serializer.getType());
// 得到序列化数组
byte[] serialize = serializer.serialize(msg);
// 写入长度
out.writeInt(serialize.length);
// 写入序列化字节数组
out.writeBytes(serialize);
}
}
解码类:
@AllArgsConstructor
public class MyDecode extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 1. 读取消息类型
short messageType = in.readShort();
// 现在还只支持request与response请求
if(messageType != MessageType.REQUEST.getCode() &&
messageType != MessageType.RESPONSE.getCode()){
System.out.println("暂不支持此种数据");
return;
}
// 2. 读取序列化的类型
short serializerType = in.readShort();
// 根据类型得到相应的序列化器
Serializer serializer = Serializer.getSerializerByCode(serializerType);
if(serializer == null)throw new RuntimeException("不存在对应的序列化器");
// 3. 读取数据序列化后的字节长度
int length = in.readInt();
// 4. 读取序列化数组
byte[] bytes = new byte[length];
in.readBytes(bytes);
// 用对应的序列化器解码字节数组
Object deserialize = serializer.deserialize(bytes, messageType);
out.add(deserialize);
}
}
这里统一的消息格式是这样的:
可以看到Encode的时候,就是按照这个顺序写入的。传输数据前需要把数据先序列化一遍。
然后使用json 序列化类:
public class JsonSerializer implements Serializer{
@Override
public byte[] serialize(Object obj) {
byte[] bytes = JSONObject.toJSONBytes(obj);
return bytes;
}
@Override
public Object deserialize(byte[] bytes, int messageType) {
Object obj = null;
// 传输的消息分为request与response
switch (messageType){
case 0:
RPCRequest request = JSON.parseObject(bytes, RPCRequest.class);
// 修bug 参数为空 直接返回
if(request.getParams() == null) return request;
Object[] objects = new Object[request.getParams().length];
// 把json字串转化成对应的对象, fastjson可以读出基本数据类型,不用转化
for(int i = 0; i < objects.length; i++){
Class<?> paramsType = request.getParamsTypes()[i];
if (!paramsType.isAssignableFrom(request.getParams()[i].getClass())){
objects[i] = JSONObject.toJavaObject((JSONObject) request.getParams()[i],request.getParamsTypes()[i]);
}else{
objects[i] = request.getParams()[i];
}
}
request.setParams(objects);
obj = request;
break;
case 1:
RPCResponse response = JSON.parseObject(bytes, RPCResponse.class);
Class<?> dataType = response.getDataType();
if(! dataType.isAssignableFrom(response.getData().getClass())){
response.setData(JSONObject.toJavaObject((JSONObject) response.getData(),dataType));
}
obj = response;
break;
default:
System.out.println("暂时不支持此种消息");
throw new RuntimeException();
}
return obj;
}
// 1 代表着json序列化方式
@Override
public int getType() {
return 1;
}
}
这里的这个反序列化为什么要这么麻烦,因为JSON序列化之后,使用的是字符串表示对象,然而response里面用的是一个data对象来储存数据,这样就会丢失data对象的信息,所以需要取得对象的类信息,然后根据类信息把Json字符串转为对象,再返回response或者request中。
这里的Response需要加一个属性,就是datatype,不然这里找不到data的类信息:
@Data
@Builder
@AllArgsConstructor
public class RPCResponse implements Serializable {
private int code;
private String message;
// 更新,这里我们需要加入这个,不然用其它序列化方式(除了java Serialize)得不到data的type
private Class<?> dataType;
private Object data;
public static RPCResponse success(Object data) {
return RPCResponse.builder().code(200).data(data).dataType(data.getClass()).build();
}
public static RPCResponse fail() {
return RPCResponse.builder().code(500).message("服务器发生错误").build();
}
}
再把原来的Netty服务器初始化里默认使用的Netty序列化改成Json序列化,服务端和客户端一样都改:
@AllArgsConstructor
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyDecode());
pipeline.addLast(new MyEncode(new JsonSerializer()));
pipeline.addLast(new NettyRPCClientHandler());
}
}
但是我在自己写项目的时候遇到了bug,因为原来的fastjson依赖用的是最新的 2.x的,然后改成了老版本的,还是不行,就不信邪,把源项目git下来,然后运行,源项目竟然可以运行,我一个对着一个看,代码都没什么错误,依赖也全换成了源项目的依赖,还是报空指针,但是使用java原生序列化就没事,用json就报错,我一步步debug过去,发现是在数据交互时的错误,
这一块:
估计还是序列化的问题。这个bug还在寻找。。。找到的时候再更新把,累了。。。