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

小编的话

在文章的最后作者为大家整理了很多资料!包括java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书等等!


有需要的朋友点这里直接去下载就好了,验证码:简书

前言

序列化:将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;
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容