关于序列化和反序列化
发布日期:2021-05-07 05:34:24 浏览次数:16 分类:精选文章

本文共 11650 字,大约阅读时间需要 38 分钟。

前言

序列化:将java对象转化为可传输的字节数组

反序列化:将字节数组还原为java对象

为啥子要序列化?

序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组

什么情况下需要序列化?

凡是需要进行跨平台存储和网络传输的数据,都需要进行序列化

本质上存储和网络传输 都需要经过 把一个对象状态保存成一种跨平台识别的字节格式,然后其他的平台才可以通过字节信息解析还原对象信息

序列化的方式

序列化只是一种拆装组装对象的规则,这种规则多种多样,常见的序列化方式有:

JDK(不支持跨语言)、JSON、XML、Hessian、Kryo(不支持跨语言)、Thrift、Protostuff、FST(不支持跨语言)

举个例子

自定义协议中,需要序列化和反序列化,案例中枚举类Algorithm的内部类重写了自定义接口Serializer中的序列化和反序列化方法,本案例中枚举类Algorithm采用了jdk和json两种序列化方式,通过配置类Config类,可以灵活在application.properties中选择序列化的方式

导入依赖

com.google.code.gson
gson
2.8.5

自定义Message类

import lombok.Data;import java.io.Serializable;import java.util.HashMap;import java.util.Map;@Datapublic abstract class Message implements Serializable {       private int sequenceId;    private int messageType;    /**     * 根据消息类型 的 数字编号,获得对应的消息 class     * @param messageType 消息类型字节     * @return 消息 class     */    public static Class
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
> 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,分别重写了接口的序列化和反序列化方法

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 
泛型 * @return */
T deSerializer(byte[] bytes, Class
clazz); /** * 序列化 * 将java对象 转换为 byte[]或json类型 */
byte[] serializer(T object); /** * 创建内部枚举类 Algorithm,实现序列化 */ enum Algorithm implements Serializer{ //java代表是自带jdk的序列化与反序列化 java{ @Override public
T deSerializer(byte[] bytes, Class
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
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 deSerializer(byte[] bytes, Class
clazz) { //将字节数组转换为字符串 String json = new String(bytes, StandardCharsets.UTF_8); return new Gson().fromJson(json,clazz); } @Override public
byte[] serializer(T object) { Gson gson = new Gson(); //将java对象转化为json字符串 String json = gson.toJson(object); //将json字符串转换为字节数组 return json.getBytes(StandardCharsets.UTF_8); } } }}

自定义协议类

自定义的协议里需要编解码,序列化的方式,此处选择了jdk和json

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.Sharablepublic class MessageCodecSharable extends MessageToMessageCodec
{ @Override protected void encode(ChannelHandlerContext ctx, Message msg, List
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 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,可灵活选择序列化的方式

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,默认是8080server.port=8080#如果为空,默认是 jdk的序列化方式serializer.algorithm=json

测试

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);    }}

实体类

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;    }}
上一篇:【JavaSE】java的反射与注解(2021.4.9)
下一篇:【JVM】JVM的学习方式和体系结构(倒计时59天——2021.4.9)

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2025年03月19日 08时16分28秒