序列化:將java對象轉化爲可傳輸的字節數組java
反序列化:將字節數組還原爲java對象面試
序列化最終的目的是爲了對象能夠跨平臺存儲,和進行網絡傳輸。而咱們進行跨平臺存儲和網絡傳輸的方式就是IO,而咱們的IO支持的數據格式就是字節數組算法
凡是須要進行跨平臺存儲和網絡傳輸的數據,都須要進行序列化json
本質上存儲和網絡傳輸 都須要通過 把一個對象狀態保存成一種跨平臺識別的字節格式,而後其餘的平臺才能夠經過字節信息解析還原對象信息api
序列化只是一種拆裝組裝對象的規則,這種規則多種多樣,常見的序列化方式有:數組
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>
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和jsonapp
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.一、採用jdk方式序列化,將java對象轉爲字節數組 //byte[] bytes = Serializer.Algorithm.java.serializer(msg); //6.二、採用json方式序列化 //byte[] bytes = Serializer.Algorithm.json.serializer(msg); //6.三、採用配置類形式,來靈活選擇使用哪一種 序列化方式 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); } }
根據搭配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); } } }
#若是爲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架構實戰電子書等等!
資料都會絕對免費分享給你們的,只但願你給做者點個三連!
歡迎關注公衆號:前程有光,領取!