文章目录

  • 简介
  • 什么是序列化
  • 重构序列化对象
  • 序列化不是加密
  • 使用真正的加密
  • 使用代理
  • Serializable和Externalizable的区别
  • 总结

简介

你知道序列化可以使用代理吗?你知道序列化的安全性吗?每个java程序员都听说过序列化,要存储对象需要序列化,要在网络上传输对象要序列化,看起来很简单的序列化其实里面还隐藏着很多小秘密,今天本文将会为大家一一揭秘。

更多精彩内容且看:

  • 区块链从入门到放弃系列教程-涵盖密码学,超级账本,以太坊,Libra,比特币等持续更新
  • Spring Boot 2.X系列教程:七天从无到有掌握Spring Boot-持续更新
  • Spring 5.X系列教程:满足你对Spring5的一切想象-持续更新
  • java程序员从小工到专家成神之路(2020版)-持续更新中,附详细文章教程

更多内容请访问www.flydean.com

什么是序列化

序列化就是将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的对象需要一个无参的构造函数。

总结

本文详细分析了序列化对象在多种情况下的使用,并讲解了Serializable和Externalizable的区别,希望大家能够喜欢。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/java-serialization/

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

你不知道的java对象序列化的秘密相关推荐

  1. java 对象怎么序列化,java对象序列化总结

    java对象序列化小结 百度百科上介绍序列化是这样的: 序列化 (Serialization): 将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储 ...

  2. 代码即财富之我学Java对象序列化与反序列化(2)

    2019独角兽企业重金招聘Python工程师标准>>> 我们在程序创建的Java对象都是存在于JVM内存中的,也就是Java对象的生命周期一定不会长于JVM,所以如何以一种持久化的方 ...

  3. 关于 Java 对象序列化您不知道的 5 件事

    数年前,当和一个软件团队一起用 Java 语言编写一个应用程序时,我体会到比一般程序员多知道一点关于 Java 对象序列化的知识所带来的好处. 关于本系列 您觉得自己懂 Java 编程?事实上,大多数 ...

  4. 深入理解Java对象序列化

    关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制.在撰写本文时,既参考了Th ...

  5. 理解Java对象序列化

    理解Java对象序列化 关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制.在 ...

  6. java对象序列化去掉字段_使用序列化查找对象中的脏字段

    java对象序列化去掉字段 假设您正在开发一个将对象自动保存到数据库中的框架. 您需要检测两次保存之间所做的更改,以便仅保存已修改的字段. 如何检测脏场. 最简单的方法是遍历原始数据和当前数据,并分别 ...

  7. Java对象序列化的本机C / C ++类似性能

    您是否曾经希望过像使用C ++这样的本地语言将Java对象转换成字节流一样快的速度? 如果您使用标准的Java序列化,您可能会对性能感到失望. Java序列化的目的是与尽可能快而紧凑地序列化对象的目的 ...

  8. JSON用于多态Java对象序列化

    长期以来,JSON已成为客户端和服务器之间各种数据序列化的事实上的标准. 除其他外,它的优势是简单和易于阅读. 但是,简单起了一些限制,我今天要谈的其中一个限制是:存储和检索多态Java对象. 让我们 ...

  9. java 对象序列化 数组_序列化-将任何对象转换为j中的字节数组

    您要执行的操作称为"序列化". 有几种方法可以做到,但是如果您不需要花哨的东西,我认为使用标准Java对象序列化就可以了. 也许您可以使用这样的东西? package com.ex ...

最新文章

  1. Cisco呼吁Arista停止在美国销售产品
  2. python小项目案例-Python小项目:快速开发出一个简单的学生管理系统
  3. tensorflow中tensor的索引
  4. android studio导出apk步骤
  5. mysql如何查询本年_mysql 查询本月、本年的数据
  6. 二叉树的广度优先遍历(层序遍历)
  7. oracle如何设置权限,ORACLE的权限设置
  8. 第一章课后习题及答案
  9. cdn回源php_别让CDN的回源把你的服务器拖垮,采用正确的回源策略
  10. CentOS常用基础命令大全
  11. 自动驾驶感知-车道线系列(一)——车道线基础流程实现
  12. sap更改主题_SAP EP 主题设置
  13. POJ1742Coins
  14. SIP - FreeSwitch 安装 编译
  15. c语言判断不是大写字母,c语言isupper()函数如何判断字符是否为大写英文字母实例...
  16. 人,总要敢于直面自己的惨淡,才能挺直腰杆
  17. 计算机一个小键盘按不出来怎么办,巧妙解决电脑键盘右边的数字键失灵问题
  18. 关于智能运维(AIOps)的学与思
  19. 5g理论速度_如何理解5G下载速度?
  20. #超级818汽车狂欢夜#易车 买车不吃亏就上易车app

热门文章

  1. 贪心算法-02活动安排问题
  2. c oracle帮助类,C#DbHelperOra,Oracle数据库帮助类
  3. POJ1265(Pick定理的应用)
  4. KdPrint 在checked 和free版本的不同
  5. __declspec(selectany)的作用
  6. 【网络编程】之三、socket网络编程
  7. COM编程之四 引用计数
  8. docker学习笔记(二)创建自己的镜像
  9. Linux Kernel TCP/IP Stack|Linux网络硬核系列
  10. DevOps和SRE有什么不同,每个意味着什么