• 关于序列化和反序列化案例看这一篇就够用了,简直讲的清新脱俗!


    前言

    序列化:将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架构实战电子书等等!

    资料都会绝对免费分享给大家的,只希望你给作者点个三连!

    欢迎关注公众号:前程有光,领取!

  • 相关阅读:
    Python 工匠:编写条件分支代码的技巧
    component-scan标签的use-default-filters属性的作用以及原理分析
    Serverless 架构的优点和缺点
    5 种使用 Python 代码轻松实现数据可视化的方法
    曾经我是一个只会excel的数据分析师,直到我遇到了……
    月薪45K的Python爬虫工程师告诉你爬虫应该怎么学,太详细了!
    用 Python 构建一个极小的区块链
    第六章 程序数据集散地;数据库
    MyBankgon功能
    第四章 深入C#的string类
  • 原文地址:https://www.cnblogs.com/lwh1019/p/14730296.html
Copyright © 2020-2023  润新知