范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

netty系列之netty中常用的对象编码解码器

  原文链接:https://mp.weixin.qq.com/s/-OxlK5XFBE_-KdC3Kq8bxg简介
  我们在程序中除了使用常用的字符串进行数据传递之外,使用最多的还是JAVA对象。在JDK中,对象如果需要在网络中传输,必须实现Serializable接口,表示这个对象是可以被序列化的。这样就可以调用JDK自身的对象对象方法,进行对象的读写。
  那么在netty中进行对象的传递可不可以直接使用JDK的对象序列化方法呢?如果不能的话,又应该怎么处理呢?
  今天带大家来看看netty中提供的对象编码器。  什么是序列化
  序列化就是将java对象按照一定的顺序组织起来,用于在网络上传输或者写入存储中。而反序列化就是从网络中或者存储中读取存储的对象,将其转换成为真正的java对象。
  所以序列化的目的就是为了传输对象,对于一些复杂的对象,我们可以使用第三方的优秀框架,比如Thrift,Protocol Buffer等,使用起来非常的方便。
  JDK本身也提供了序列化的功能。要让一个对象可序列化,则可以实现java.io.Serializable接口。
  java.io.Serializable是从JDK1.1开始就有的接口,它实际上是一个marker interface,因为java.io.Serializable并没有需要实现的接口。继承java.io.Serializable就表明这个class对象是可以被序列化的。  @Data @AllArgsConstructor public class CustUser implements java.io.Serializable{     private static final long serialVersionUID = -178469307574906636L;     private String name;     private String address; }
  上面我们定义了一个CustUser可序列化对象。这个对象有两个属性:name和address。
  接下看下怎么序列化和反序列化:  public void testCusUser() throws IOException, ClassNotFoundException {         CustUser custUserA=new CustUser("jack","www.flydean.com");         CustUser custUserB=new CustUser("mark","www.flydean.com");          try(FileOutputStream fileOutputStream = new FileOutputStream("target/custUser.ser")){             ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);             objectOutputStream.writeObject(custUserA);             objectOutputStream.writeObject(custUserB);         }          try(FileInputStream fileInputStream = new FileInputStream("target/custUser.ser")){             ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);             CustUser custUser1 = (CustUser) objectInputStream.readObject();             CustUser custUser2 = (CustUser) objectInputStream.readObject();             log.info("{}",custUser1);             log.info("{}",custUser2);         }     }
  上面的例子中,我们实例化了两个CustUser对象,并使用objectOutputStream将对象写入文件中,最后使用ObjectInputStream从文件中读取对象。
  上面是最基本的使用。需要注意的是CustUser class中有一个serialVersionUID字段。
  serialVersionUID是序列化对象的唯一标记,如果class中定义的serialVersionUID和序列化存储中的serialVersionUID一致,则表明这两个对象是一个对象,我们可以将存储的对象反序列化。
  如果我们没有显示的定义serialVersionUID,则JVM会自动根据class中的字段,方法等信息生成。很多时候我在看代码的时候,发现很多人都将serialVersionUID设置为1L,这样做是不对的,因为他们没有理解serialVersionUID的真正含义。  重构序列化对象
  假如我们有一个序列化的对象正在使用了,但是突然我们发现这个对象好像少了一个字段,要把他加上去,可不可以加呢?加上去之后原序列化过的对象能不能转换成这个新的对象呢?
  答案是肯定的,前提是两个版本的serialVersionUID必须一样。新加的字段在反序列化之后是空值。  序列化不是加密
  有很多同学在使用序列化的过程中可能会这样想,序列化已经将对象变成了二进制文件,是不是说该对象已经被加密了呢?
  这其实是序列化的一个误区,序列化并不是加密,因为即使你序列化了,还是能从序列化之后的数据中知道你的类的结构。比如在RMI远程调用的环境中,即使是class中的private字段也是可以从stream流中解析出来的。
  如果我们想在序列化的时候对某些字段进行加密操作该怎么办呢?
  这时候可以考虑在序列化对象中添加writeObject和readObject方法:  private String name;     private String address;     private int age;      private void writeObject(ObjectOutputStream stream)             throws IOException     {         //给age加密         age = age + 2;         log.info("age is {}", age);         stream.defaultWriteObject();     }      private void readObject(ObjectInputStream stream)             throws IOException, ClassNotFoundException     {         stream.defaultReadObject();         log.info("age is {}", age);         //给age解密         age = age - 2;     }
  上面的例子中,我们为CustUser添加了一个age对象,并在writeObject中对age进行了加密(加2),在readObject中对age进行了解密(减2)。
  注意,writeObject和readObject都是private void的方法。他们的调用是通过反射来实现的。  使用真正的加密
  上面的例子, 我们只是对age字段进行了加密,如果我们想对整个对象进行加密有没有什么好的处理办法呢?
  JDK为我们提供了javax.crypto.SealedObject 和java.security.SignedObject来作为对序列化对象的封装。从而将整个序列化对象进行了加密。
  还是举个例子:  public void testCusUserSealed() throws IOException, ClassNotFoundException, NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {         CustUser custUserA=new CustUser("jack","www.flydean.com");         Cipher enCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");         Cipher deCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");         SecretKey secretKey = new SecretKeySpec("saltkey111111111".getBytes(), "AES");         IvParameterSpec iv = new IvParameterSpec("vectorKey1111111".getBytes());         enCipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);         deCipher.init(Cipher.DECRYPT_MODE,secretKey,iv);         SealedObject sealedObject= new SealedObject(custUserA, enCipher);          try(FileOutputStream fileOutputStream = new FileOutputStream("target/custUser.ser")){             ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);             objectOutputStream.writeObject(sealedObject);         }          try(FileInputStream fileInputStream = new FileInputStream("target/custUser.ser")){             ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);             SealedObject custUser1 = (SealedObject) objectInputStream.readObject();             CustUser custUserV2= (CustUser) custUser1.getObject(deCipher);             log.info("{}",custUserV2);         }     }
  上面的例子中,我们构建了一个SealedObject对象和相应的加密解密算法。
  SealedObject就像是一个代理,我们写入和读取的都是这个代理的加密对象。从而保证了在数据传输过程中的安全性。  使用代理
  上面的SealedObject实际上就是一种代理,考虑这样一种情况,如果class中的字段比较多,而这些字段都可以从其中的某一个字段中自动生成,那么我们其实并不需要序列化所有的字段,我们只把那一个字段序列化就可以了,其他的字段可以从该字段衍生得到。
  在这个案例中,我们就需要用到序列化对象的代理功能。
  首先,序列化对象需要实现writeReplace方法,表示替换成真正想要写入的对象:  public class CustUserV3 implements java.io.Serializable{      private String name;     private String address;      private Object writeReplace()             throws java.io.ObjectStreamException     {         log.info("writeReplace {}",this);         return new CustUserV3Proxy(this);     } }
  然后在Proxy对象中,需要实现readResolve方法,用于从系列化过的数据中重构序列化对象。如下所示:  public class CustUserV3Proxy implements java.io.Serializable{      private String data;      public CustUserV3Proxy(CustUserV3 custUserV3){         data =custUserV3.getName()+ "," + custUserV3.getAddress();     }      private Object readResolve()             throws java.io.ObjectStreamException     {         String[] pieces = data.split(",");         CustUserV3 result = new CustUserV3(pieces[0], pieces[1]);         log.info("readResolve {}",result);         return result;     } }
  我们看下怎么使用:  public void testCusUserV3() throws IOException, ClassNotFoundException {         CustUserV3 custUserA=new CustUserV3("jack","www.flydean.com");          try(FileOutputStream fileOutputStream = new FileOutputStream("target/custUser.ser")){             ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);             objectOutputStream.writeObject(custUserA);         }          try(FileInputStream fileInputStream = new FileInputStream("target/custUser.ser")){             ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);             CustUserV3 custUser1 = (CustUserV3) objectInputStream.readObject();             log.info("{}",custUser1);         }     }
  注意,我们写入和读出的都是CustUserV3对象。  Serializable和Externalizable的区别
  最后我们讲下Externalizable和Serializable的区别。Externalizable继承自Serializable,它需要实现两个方法:   void writeExternal(ObjectOutput out) throws IOException;  void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
  什么时候需要用到writeExternal和readExternal呢?
  使用Serializable,Java会自动为类的对象和字段进行对象序列化,可能会占用更多空间。而Externalizable则完全需要我们自己来控制如何写/读,比较麻烦,但是如果考虑性能的话,则可以使用Externalizable。
  另外Serializable进行反序列化不需要执行构造函数。而Externalizable需要执行构造函数构造出对象,然后调用readExternal方法来填充对象。所以Externalizable的对象需要一个无参的构造函数。  netty中对象的传输
  在上面的序列化一节中,我们已经知道了对于定义好的JAVA对象,我们可以通过使用ObjectOutputStream和ObjectInputStream来实现对象的读写工作,那么在netty中是否也可以使用同样的方式来进行对象的读写呢?
  很遗憾的是,在netty中并不能直接使用JDK中的对象读写方法,我们需要对其进行改造。
  这是因为我们需要一个通用的对象编码和解码器,如果使用ObjectOutputStream和ObjectInputStream,因为不同对象的结构是不一样的,所以我们在读取对象的时候需要知道读取数据的对象类型才能进行完美的转换。
  而在netty中我们需要的是一种更加通用的编码解码器,那么应该怎么做呢?
  还记得之前我们在讲解通用的frame decoder中讲过的LengthFieldBasedFrameDecoder? 通过在真实的数据前面加上数据的长度,从而达到根据数据长度进行frame区分的目的。
  netty中提供的编码解码器名字叫做ObjectEncoder和ObjectDecoder,先来看下他们的定义:  public class ObjectEncoder extends MessageToByteEncoder { public class ObjectDecoder extends LengthFieldBasedFrameDecoder {
  可以看到ObjectEncoder继承自MessageToByteEncoder,其中的泛型是Serializable,表示encoder是从可序列化的对象encode成为ByteBuf。
  而ObjectDecoder正如上面我们所说的继承自LengthFieldBasedFrameDecoder,所以可以通过一个长度字段来区分实际要读取对象的长度。
  接下来我们详细了解一下这两个类是如何工作的。  ObjectEncoder
  先来看ObjectEncoder是如何将一个对象序列化成为ByteBuf的。
  根据LengthFieldBasedFrameDecoder的定义,我们需要一个数组来保存真实数据的长度,这里使用的是一个4字节的byte数组叫做LENGTH_PLACEHOLDER,如下所示:  private static final byte[] LENGTH_PLACEHOLDER = new byte[4];
  我们看下它的encode方法的实现:      protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception {         int startIdx = out.writerIndex();          ByteBufOutputStream bout = new ByteBufOutputStream(out);         ObjectOutputStream oout = null;         try {             bout.write(LENGTH_PLACEHOLDER);             oout = new CompactObjectOutputStream(bout);             oout.writeObject(msg);             oout.flush();         } finally {             if (oout != null) {                 oout.close();             } else {                 bout.close();             }         }         int endIdx = out.writerIndex();         out.setInt(startIdx, endIdx - startIdx - 4);     }
  这里首先创建了一个ByteBufOutputStream,然后向这个Stream中写入4字节的长度字段,接着将ByteBufOutputStream封装到CompactObjectOutputStream中。
  CompactObjectOutputStream是ObjectOutputStream的子类,它重写了writeStreamHeader和writeClassDescriptor两个方法。
  CompactObjectOutputStream将最终的数据msg写入流中,一个encode的过程就差不多完成了。
  为什么说差不多完成了呢?因为长度字段还是空的。
  在最开始的时候,我们只是写入了一个长度的placeholder,这个placeholder是空的,并没有任何数据,这个数据是在最后一步out.setInt中写入的:  out.setInt(startIdx, endIdx - startIdx - 4);
  这种实现也给了我们一种思路,在我们还不知道消息的真实长度的时候,如果希望在消息之前写入消息的长度,可以先占个位置,等消息全部读取完毕,知道真实的长度之后,再替换数据。
  到此,对象数据已经全部编码完毕,接下来我们看一下如何从编码过后的数据中读取对象。  ObjectDecoder
  之前说过了ObjectDecoder继承自LengthFieldBasedFrameDecoder,它的decode方法是这样的:      protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {         ByteBuf frame = (ByteBuf) super.decode(ctx, in);         if (frame == null) {             return null;         }          ObjectInputStream ois = new CompactObjectInputStream(new ByteBufInputStream(frame, true), classResolver);         try {             return ois.readObject();         } finally {             ois.close();         }     }
  首先调用LengthFieldBasedFrameDecoder的decode方法,根据对象的长度,读取到真实的对象数据放到ByteBuf中。
  然后通过自定义的CompactObjectInputStream从ByteBuf中读取到真实的对象,并返回。
  CompactObjectInputStream继承自ObjectInputStream,是和CompactObjectOutputStream相反的操作。  ObjectEncoderOutputStream和ObjectDecoderInputStream
  ObjectEncoder和ObjectDecoder是对象和ByteBuf之间的转换,netty还提供了和ObjectEncoder,ObjectDecoder兼容的ObjectEncoderOutputStream和ObjectDecoderInputStream,这两个类可以从stream中对对象编码和解码,并且和ObjectEncoder,ObjectDecoder完全兼容的。  总结
  以上就是netty中提供的对象编码和解码器,大家如果希望在netty中传递对象,那么netty提供的这两个编码解码器是最好的选择。

这5件鸡肋家居用品,你都不舍得扔,谈什么精致生活现在有很多人都在进行精致生活,但是也有很多人喜欢勤俭持家,这是中国的传统美德!但如果你什么都不扔,家里还有地方用吗?其实勤俭持家的最终目的,就是提高我们的生活品质。我们需要扔掉已经房子不一定要大,若想看起来上档次,这两个地方多费点钱如果你现在买房子,是不是越大越好呢?很多人都是这样的想法,其实大家的房子很招人喜欢!但如果你家人口不多的话,建议买小一点,不然看着空洞洞的房间,没有人气很失落的。房子不一定要大,若冰箱应该怎么选?十字开门对比对开门,终于知道答案了冰箱作为家里必不可少的电器,每次买回家的食材和瓜果都会放在冰箱里。现在市面上的品牌和款式非常多,很多人看得眼都花了,不知道如何选!其实很多人都在纠结,十字开门和双开门到底哪个更好一马桶内壁上的黄渍很难清洁?试试这样做,马桶洁净如新,非常管用卫生间是我们生活当中必不可少的场所,从早到晚都会上厕所,如果在这个时候,发现厕所不干净,会影响我们的心情差,影响我们的工作!所以有一个干净整洁的马桶,非常的重要!但是长时间的使用,马桶作为卫生间的核心区,上不上档次,关键看这3个小细节马桶作为家庭卫生间必不可少的卫具,是卫生间最核心的位置。但是马桶周边的卫生情况很让人头大!如果做不好,那么卫生间就会弄得很邋遢!其实想要卫生间干净整洁,也很简单,只要经这三个小细节4个代表富裕的家居物件,有两个以上,说明你家属于有钱人现在人们的生活水平普遍提高,很多家庭都开始进入精致生活!这简单的就可以注意到很多家庭的居家卫生更干净整洁了!最主要的就是因为很多居家主妇都在使用好用的家居物件,可以减少很多家务负担为什么吉祥时刻就想要喝王老吉?在同质化的竞争中,品牌符号能够帮助人们快速识别品牌,甚至做购买决策。而在现在品牌竞争激烈且同质化严重的今天,具有辨识度的品牌符号能够将品牌价值观植入到用户心里,让独特的品牌符号为品卫生间这4个地方不要空着,用心设计下,可以腾出很多空间很多精致的女人都喜欢经家里收拾得干干净净。特别是家里使用频率最高的卫生间,每次都要把它清洗干净,整洁,无异味。这样可以让生活更温馨!卫生间这4个地方不要空着,用心设计下,可以腾出很日本人设计的这些家居小用品,中国人难以理解,但实用性很强不得不佩服日本人的设计能力,特别是日本的家庭主妇,她们总是会用各种各样的小玩意解决居家难题,开始我们很难理解,但是用过之后,真的实用性非常强!并不是我崇洋媚外,而是想要借鉴他们的好洗衣服不要只倒洗衣液!教你正确做法,洗衣干净又卫生现在智能居家走进生活,特别是洗衣服,很多人都在用洗衣机洗,但是往往很多顽固的污渍清洗不干净,会导致很多昂贵的衣服也要需要手洗,但是很多人洗衣服只会使用洗衣液,这样是洗不干净的,教你厨房也可以成网红!这4个最受欢迎的家居小用品,你用过几个?如今的网红非常多,就连厨房也可以成网红,比如这四个最受欢迎的家居小物品,你用过几个?1防溅水龙头大家出门都有水龙头,但是普通的水龙头缺点很多。比如用的时候控制不住水,四处喷溅,一不
超实用!这是我见过最全面的Python入门教程,新手小白不要错过大学毕业后相当迷茫,不知道做些什么,于是学长推荐去学Python,当年2w买的python教程,三个月的时间成功上岸,现在用不着了,送给有缘人,不要钱,只愿你和我一样上岸本套视频共一部手机最长能用几年?遇到这4种情况,你还是换部新手机吧现在,智能手机在不断的发展,那么我们手中的智能手机,最长能用几年?毕竟就现在的手机的发展,我们不可能也没必要每出一个新机型,就选择更换新手机。一般情况下,安卓手机的使用寿命大约三年美国对中国使用顶级网络武器证据曝光美国国安局最强大互联网攻击工具曝光!你的社交账户很可能在美国监控之下22日,360政企安全集团首次对外界完全披露美国国家安全局(NSA)针对中国境内目标所使用的代表性网络武器Qua蚂蚁财富金选投顾调整优化6家机构更新投顾策略基金池及名称等信息将上线蚂蚁投顾管家21世纪经济报道记者姜诗蔷北京报道蚂蚁财富金选投顾调整后将重新上线。3月23日,兴证全球基金南方基金中欧财富财通证券广发基金嘉实财富6家公司发布了调整旗下相关基金投顾策略的公告,其持斯拉汽车由俄乌战争联想到的可怕之处马斯克的星链系统已经在俄乌战争中为乌克兰提供了大量战场信息情报,乌军也通过情报知晓俄军的动态部署,为乌军作战决策提供了重要参考。我想说的是,目前特斯拉汽车的自动辅助驾驶系统已达到L英伟达变软公布多款开发工具,瞄准科研游戏开发场景记者彭新编辑AI芯片巨头英伟达变软,发布多项企业级软件产品。3月23日,在游戏开发者大会(GDC)和GPU技术大会(GTC)上,英伟达公布多款开发工具,瞄准游戏科研工业需求。这些工互联网和游戏研究报告2019年中国网民搜索引擎使用情况研究报告。pdf2020年度互联网企业信息科技风险治理现状调研报告互联网协会。pdf2021中国网络视听发展研究报告。pdf2021产业互联网实践腾讯Q4财报电话会不会启动股票回购计划,游戏版号停发是暂时的3月23日周三,腾讯控股发布2021财年第四季度业绩及全年财报。其美股ADR盘初一度跌8。7,随后跌幅收窄至不足4。这主要是由于第四季度营收增速创新低,非国际财务报告准则(NonI协昌科技功率芯片大客户频繁变更研发费率不及3两起专利纠纷未决长江商报消息长江商报记者张璐已经八次递交招股书,却很难通过考核的协昌科技,终于迎来了曙光。3月17日,江苏协昌电子科技股份有限公司(简称协昌科技)创业板首发上会获得通过。长江商报记iPhone13ProMax与iPhone12ProMax详细对比区别一目了然iPhone13ProMax是苹果迄今为止发布的最好的iPhone手机,这得益于其升级的摄像头系统超大屏幕和出色的性能。这绝对比iPhone12ProMax更上一层楼,这一点是显而小米米家滚筒洗衣机10Kg发布DD直驱电机首发仅1479元3月21日消息,小米发布了米家滚筒洗衣机10Kg,将于明天(3月22日)10点开启预售,原价1699元,首发到手价仅1479元。该洗衣机有三大卖点1直驱电机源头降噪洗涤更安静2除菌