前言
序列化:将 java 对象转化为可传输的字节数组
反序列化:将字节数组还原为 java 对象
为啥子要序列化?
序列化最终的目标是为了对象能够跨平台存储,和进行网络传输。而咱们进行跨平台存储和网络传输的形式就是 IO,而咱们的 IO 反对的数据格式就是字节数组
什么状况下须要序列化?
但凡须要进行跨平台存储和网络传输的数据,都须要进行序列化
实质上存储和网络传输 都须要通过 把一个对象状态保留成一种跨平台辨认的字节格局,而后其余的平台才能够通过字节信息解析还原对象信息
序列化的形式
序列化只是一种拆装组装对象的规定,这种规定多种多样,常见的序列化形式有:
JDK(不反对跨语言)、JSON、XML、Hessian、Kryo(不反对跨语言)、Thrift、Protostuff、FST(不反对跨语言)
举个栗子
自定义协定中,须要序列化和反序列化,案例中枚举类 Algorithm 的外部类重写了自定义接口 Serializer 中的序列化和反序列化办法,本案例中枚举类 Algorithm 采纳了 jdk 和 json 两种序列化形式,通过配置类 Config 类,能够灵便在 application.properties 中抉择序列化的形式
导入依赖
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
自定义 Message 类
package com.lian.chatroom.message;
import lombok.Data;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@Data
public abstract class Message implements Serializable {
private int sequenceId;
private int messageType;
/**
* 依据音讯类型 的 数字编号,取得对应的音讯 class
* @param messageType 音讯类型字节
* @return 音讯 class
*/
public static Class<? extends Message> getMessageClass(int messageType) {return messageClasses.get(messageType);
}
// 定义形象办法,获取返回音讯类型
public abstract int getMessageType();
// 自定义动态常量,每种数据类型以数字代表
public static final int LoginRequestMessage = 0;
public static final int LoginResponseMessage = 1;
public static final int ChatRequestMessage = 2;
public static final int ChatResponseMessage = 3;
public static final int GroupCreateRequestMessage = 4;
public static final int GroupCreateResponseMessage = 5;
public static final int GroupJoinRequestMessage = 6;
public static final int GroupJoinResponseMessage = 7;
public static final int GroupQuitRequestMessage = 8;
public static final int GroupQuitResponseMessage = 9;
public static final int GroupChatRequestMessage = 10;
public static final int GroupChatResponseMessage = 11;
public static final int GroupMembersRequestMessage = 12;
public static final int GroupMembersResponseMessage = 13;
public static final int PingMessage = 14;
public static final int PongMessage = 15;
/**
* 申请类型 byte 值
*/
public static final int RPC_MESSAGE_TYPE_REQUEST = 101;
/**
* 响应类型 byte 值
*/
public static final int RPC_MESSAGE_TYPE_RESPONSE = 102;
//map 存储(音讯类型数字编号,音讯类型)private static final Map<Integer, Class<? extends Message>> messageClasses = new HashMap<>();
//static 代码块随着类的加载而执行,而且只执行一次
static {messageClasses.put(LoginRequestMessage, LoginRequestMessage.class);
messageClasses.put(LoginResponseMessage, LoginResponseMessage.class);
messageClasses.put(ChatRequestMessage, ChatRequestMessage.class);
messageClasses.put(ChatResponseMessage, ChatResponseMessage.class);
messageClasses.put(GroupCreateRequestMessage, GroupCreateRequestMessage.class);
messageClasses.put(GroupCreateResponseMessage, GroupCreateResponseMessage.class);
messageClasses.put(GroupJoinRequestMessage, GroupJoinRequestMessage.class);
messageClasses.put(GroupJoinResponseMessage, GroupJoinResponseMessage.class);
messageClasses.put(GroupQuitRequestMessage, GroupQuitRequestMessage.class);
messageClasses.put(GroupQuitResponseMessage, GroupQuitResponseMessage.class);
messageClasses.put(GroupChatRequestMessage, GroupChatRequestMessage.class);
messageClasses.put(GroupChatResponseMessage, GroupChatResponseMessage.class);
messageClasses.put(GroupMembersRequestMessage, GroupMembersRequestMessage.class);
messageClasses.put(GroupMembersResponseMessage, GroupMembersResponseMessage.class);
messageClasses.put(RPC_MESSAGE_TYPE_REQUEST, RpcRequestMessage.class);
messageClasses.put(RPC_MESSAGE_TYPE_RESPONSE, RpcResponseMessage.class);
}
}
自定义序列化接口
自定义枚举类 Algorithm,而枚举类 Algorithm 也有两个外部类对象 java 和 json,别离重写了接口的序列化和反序列化办法
package com.lian.chatroom.protocol;
import com.google.gson.Gson;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
* 为了反对更多的序列化办法
*/
public interface Serializer {
/**
* 反序列化
* 将 byte[] 或 json 转换为 java 对象
* @param bytes 字节数组
* @param clazz 要转换成的 java 对象类型
* @param <T> 泛型
* @return
*/
<T> T deSerializer(byte[] bytes, Class<T> clazz);
/**
* 序列化
* 将 java 对象 转换为 byte[] 或 json 类型
*/
<T> byte[] serializer(T object);
/**
* 创立外部枚举类 Algorithm,实现序列化
*/
enum Algorithm implements Serializer{
//java 代表是自带 jdk 的序列化与反序列化
java{
@Override
public <T> T deSerializer(byte[] bytes, Class<T> clazz) {
try {ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
// 对象输入流读取 java 对象
return (T) ois.readObject();} catch (IOException | ClassNotFoundException e) {e.printStackTrace();
throw new RuntimeException("反序列化失败", e);
}
}
@Override
public <T> byte[] serializer(T object) {
try {ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
// 将 java 对象写入到对象输入流中
oos.writeObject(object);
byte[] bytes = bos.toByteArray(); // 返回字节数组
return bytes;
} catch (IOException e) {throw new RuntimeException("序列化失败", e);
}
}
},
json{
@Override
public <T> T deSerializer(byte[] bytes, Class<T> clazz) {
// 将字节数组转换为字符串
String json = new String(bytes, StandardCharsets.UTF_8);
return new Gson().fromJson(json,clazz);
}
@Override
public <T> byte[] serializer(T object) {Gson gson = new Gson();
// 将 java 对象转化为 json 字符串
String json = gson.toJson(object);
// 将 json 字符串转换为字节数组
return json.getBytes(StandardCharsets.UTF_8);
}
}
}
}
自定义协定类
自定义的协定里须要编解码,序列化的形式,此处抉择了 jdk 和 json
package com.lian.chatroom.protocol;
import com.lian.chatroom.config.Config;
import com.lian.chatroom.message.Message;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
/**
* 必须和 LengthFieldBasedFrameDecoder 一起应用,确保接到的 ByteBuf 音讯是残缺的
* 音讯编解码
* 出栈:ByteBuf 格局数据 转换为 字符串等其余格局 解码
* 入栈:字符串等其余格局 转换为 ByteBuf 格局数据 编码
*/
@Slf4j
@ChannelHandler.Sharable
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, Message> {
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {
// 用通道调配一个缓存区
ByteBuf out = ctx.alloc().buffer();
//1. 4 字节的魔数,就是服务端和客户端约定好的暗号,例如:天王盖地虎 宝塔镇魔妖
out.writeBytes(new byte[]{1, 2, 3, 4});
// 2. 1 字节的版本,
out.writeByte(1);
// 3. 1 字节的序列化形式 jdk 0 , json 1
//out.writeByte(0); // 写死的形式
//3.1 采纳配置类灵便抉择序列化形式,返回此枚举常量的序号,如果序列化形式是 jdk 就会填写 0,如果是 json 就会填写 1
out.writeByte(Config.getSerializerAlgorithm().ordinal());
// 4. 1 字节的指令类型
out.writeByte(msg.getMessageType());
// 5. 4 个字节
out.writeInt(msg.getSequenceId());
// 无意义,对齐填充
out.writeByte(0xff);
// 6. 获取内容的字节数组
// ByteArrayOutputStream bos = new ByteArrayOutputStream();
// ObjectOutputStream oos = new ObjectOutputStream(bos);
// oos.writeObject(msg);
// byte[] bytes = bos.toByteArray();
//6.1、采纳 jdk 形式序列化,将 java 对象转为字节数组
//byte[] bytes = Serializer.Algorithm.java.serializer(msg);
//6.2、采纳 json 形式序列化
//byte[] bytes = Serializer.Algorithm.json.serializer(msg);
//6.3、采纳配置类模式,来灵便抉择应用哪种 序列化形式
byte[] bytes = Config.getSerializerAlgorithm().serializer(msg);
// 7. 长度
out.writeInt(bytes.length);
// 8. 将字节数组写入到缓存区
out.writeBytes(bytes);
outList.add(out);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {int magicNum = in.readInt();
byte version = in.readByte();
// 从缓存区中读取到编码时用的哪种序列化算法类型,是 jdk or json
// 返回 0 or 1, 0 代表 jdk 序列化形式,1 代表 json 序列化形式
byte serializerAlgorithm = in.readByte();
// 音讯类型,0,1,2,。。。byte messageType = in.readByte();
int sequenceId = in.readInt();
// 从缓存区读取字节数组数据
in.readByte();
// 获取缓存区内字节数组的大小
int length = in.readInt();
// 生成和缓冲区数据大小雷同的 byte 数组,将缓存区内数据 封装到 byte 数组
byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
// ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
// Message message = (Message) ois.readObject();
// 采纳 jdk 形式反序列化,将 byte 数组转为 Message 对象
//Message message = Serializer.Algorithm.java.deSerializer(bytes, Message.class);
// 采纳 json 形式反序列化
//Message message = Serializer.Algorithm.json.deSerializer(bytes, Message.class);
// 采纳配置类灵便抉择应用哪种序列化形式进行解码
//values 返回全副序列化形式,下标为 0 就是 jdk 形式,下标为 1 就是 json 形式,必须和序列化的编解码形式雷同
//Serializer.Algorithm.values()[serializerAlgorithm] 找到反序列化形式算法,是 jdk 还是 json
//Message.getMessageClass(messageType) 确定具体音讯类型
Message message = Serializer.Algorithm.values()[serializerAlgorithm].deSerializer(bytes, Message.getMessageClass(messageType));
log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerAlgorithm, messageType, sequenceId, length);
log.debug("{}", message);
out.add(message);
}
}
配置类 Config
依据搭配 application.properties,可灵便抉择序列化的形式
package com.lian.chatroom.config;
import com.lian.chatroom.protocol.Serializer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 此类作用
* 序列化形式有很多种,配置类能够灵便设置 选用哪种序列化形式,代替间接在 MessageCodecSharable 协定类里批改
*/
public abstract class Config {
static Properties properties;
static {
try {
// 加载本类下的资源文件
InputStream inputStream = Config.class.getResourceAsStream("/application.properties");
properties = new Properties();
properties.load(inputStream);
} catch (IOException e) {throw new RuntimeException(e);
}
}
public static int getSetverPort(){String value = properties.getProperty("server.port");
if (value == null){return 8080;}else {return Integer.parseInt(value);
// return Integer.valueOf(value);
}
}
public static Serializer.Algorithm getSerializerAlgorithm(){String value = properties.getProperty("serializer.algorithm");
if (value == null){return Serializer.Algorithm.java;}else {return Serializer.Algorithm.valueOf(value);
}
}
}
application.properties
# 如果为 null,默认是 8080
server.port=8080
#如果为空,默认是 jdk 的序列化形式
serializer.algorithm=json
测试
package com.lian.chatroom;
import com.lian.chatroom.message.LoginRequestMessage;
import com.lian.chatroom.protocol.MessageCodecSharable;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.logging.LoggingHandler;
import org.junit.jupiter.api.Test;
public class TestSerializer {
@Test
public void encode() {MessageCodecSharable Codec = new MessageCodecSharable();
LoggingHandler LOGGING = new LoggingHandler();
//EmbeddedChannel 是 netty 专门改良针对 ChannelHandler 的单元测试而提供的
EmbeddedChannel channel = new EmbeddedChannel(LOGGING, Codec, LOGGING);
LoginRequestMessage message = new LoginRequestMessage("zhangsan", "123");
channel.writeOutbound(message);
}
}
实体类
package com.lian.chatroom.message;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 登录申请音讯,须要用户名和明码
*
* 客户端和服务端建立联系后,客户端向服务端发送一个登录申请的音讯
* 用户名和明码正确,登录胜利,持续进行下一步聊天业务
* 登录失败,就退出提醒从新登录
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString(callSuper = true)
public class LoginRequestMessage extends Message{
private String username;
private String password;
// 获取音讯类型
@Override
public int getMessageType() {return LoginRequestMessage;}
}
最初
在文章的最初作者为大家整顿了很多材料!包含 java 外围知识点 + 全套架构师学习材料和视频 + 一线大厂面试宝典 + 面试简历模板 + 阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题 +Spring 源码合集 +Java 架构实战电子书等等!
材料都会相对收费分享给大家的,只心愿你给作者点个三连!
欢送关注公众号:前程有光,支付!