小编的话
在文章的最后作者为大家整理了很多资料!包括java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书等等!
有需要的朋友 点这里直接去下载就好了,验证码:
前言
序列化:将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类
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> 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 泛型
* @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
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 {
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, List
配置类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;
}
}