Java序列化、反序列化机制

r囧r小猫 2023-01-20 12:51 79阅读 0赞

序列化、反序列化

文章目录

  • 序列化、反序列化
    • 一、概念
    • 二、如何实现序列化
    • 三、Serializable的作用
    • 四、serialVersionUID号的作用
    • 五、不会被序列化的情况
    • 六、序列化的受控和加强
    • 七、继承关系序列化问题
    • 八、反序列化创建对象的方式

一、概念

  • 序列化

    • 把Java对象转换为与平台无关的二进制流,更便于跨平台存储和网络传播
  • 反序列化

    • 把二进制流恢复为原先的Java对象

二、如何实现序列化

  • 将待序列化的类实现Serializable接口

    1. public class Student implements Serializable {
    2. private String name;
    3. private Integer age;
    4. private Integer score;
    5. @Override
    6. public String toString() {
    7. return "Student:" + 'n' +
    8. "name = " + this.name + 'n' +
    9. "age = " + this.age + 'n' +
    10. "score = " + this.score + 'n'
    11. ;
    12. }
    13. // ... 其他省略 ...
    14. }
  • 序列化方法

    1. public static void serialize( ) throws IOException {
    2. Student student = new Student();
    3. student.setName("周杰伦");
    4. student.setAge( 18 );
    5. student.setScore( 1000 );
    6. ObjectOutputStream objectOutputStream =
    7. new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    8. objectOutputStream.writeObject( student );
    9. objectOutputStream.close();
    10. }
  • 反序列化方法

    1. public static void deserialize( ) throws IOException, ClassNotFoundException {
    2. ObjectInputStream objectInputStream =
    3. new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    4. Student student = (Student) objectInputStream.readObject();
    5. objectInputStream.close();
    6. System.out.println("反序列化结果为:" + student);
    7. }
  • 运行结果

    1. 反序列化结果为:
    2. Student:
    3. name = 周杰伦
    4. age = 18
    5. score = 1000

三、Serializable的作用

  • 如果上述的 Student 类没有实现 Serializable 接口,程序运行会报错

    image-20210508215451151

  • 根据错误提示,进入源码 ObjectOutputStreamwriteObject0() 方法,部分内容如下

    image-20210508214115332

    根据源码内容,可以得到如下结论:如果一个对象既不是字符串数组枚举,而且也没有实现 Serializable 接口的话,在序列化时就会抛出 NotSerializableException 异常

  • Serializable 接口也仅仅只是做一个标记用,它告诉代码只要是实现了 Serializable 接口的类都是可以被序列化的,然而真正的序列化动作不需要靠它完成

四、serialVersionUID号的作用

在某些 JDK 自带的类中,经常会看到一个名为 serialVersionUID 的字段:

  1. private static final long serialVersionUID = -4392658638228508589L;

对于上述 Student 类,并没有显示的声明一个 serialVersionUID ,发现序列化 / 反序列化一切正常。如果将对象序列化到本地后,在反序列化之前对 Student 类做任意的修改(比如添加属性或者修改属性名等操作),在反序列化时会报错,如下所示:

image-20210508215908371

错误内容表示序列化前后的 serialVersionUID 号码不兼容

  • 因此可以得到如下三个结论:

    • serialVersionUID是序列化前后的唯一标识符

      • serialVersionUID序列化ID,可以看成是序列化和反序列化过程中的“暗号”,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程
    • 如果没有人为显式定义过 serialVersionUID,那编译器会为它自动声明一个

      • 如果在定义一个可序列化的类时,没有人为显式地给它定义一个 serialVersionUID 的话,则Java运行时环境会根据该类的各方面信息自动地为它生成一个默认的 serialVersionUID,但是一旦更改了类的结构或者信息,则类的 serialVersionUID 也会跟着变化,这也是上述报错的原因
      • 所以,为了 serialVersionUID 的确定性,写代码时还是建议凡是 implements Serializable 的类,都最好人为显式地为它声明一个 serialVersionUID 明确值
    • 只要序列化版本号一致,修改类结构并不会影响反序列化对象

五、不会被序列化的情况

  • 凡是被 static 修饰的字段是不会被序列化的

    • 序列化保存的是对象的状态而非类的状态,所以会忽略 static 静态域也是理所应当的
    • 反序列化得到的值读取的是虚拟机里面的此字段的值,所以 String 型不一定为 null 值
  • 凡是被 transient 修饰的字段是不会被序列化的

    • 如果在序列化某个类的对象时,不希望某个字段被序列化(比如这个字段存放的是隐私值,如密码等),那这时就可以用 transient 修饰符来修饰该字段
    • 反序列化得到的字段值采用默认值,比如 String 型为 null 值

六、序列化的受控和加强

  • 从上面的过程可以看出,序列化和反序列化的过程其实是有漏洞的,因为从序列化到反序列化是有中间过程的,如果被别人拿到了中间字节流,然后加以伪造或者篡改,那反序列化出来的对象就会有一定风险了
  • 解决方式是自行编写 readObject() 函数,用于对象的反序列化构造,从而提供约束性,既然自行编写 readObject() 函数,那就可以做很多可控的事情:比如各种判断工作,如下:

    1. //一般来说学生的成绩应该在0 ~ 100之间,我们为了防止学生的考试成绩在反序列化时被别人篡改成不符合常规的值,可以自行编写readObject()函数用于反序列化的控制
    2. private void readObject( ObjectInputStream objectInputStream ) throws IOException, ClassNotFoundException {
    3. // 调用默认的反序列化函数
    4. objectInputStream.defaultReadObject();
    5. // 手工检查反序列化后学生成绩的有效性,若发现有问题,即终止操作!
    6. if( 0 > score || 100 < score ) {
    7. throw new IllegalArgumentException("学生分数只能在0到100之间!");
    8. }
    9. }

    此时故意将学生的分数改为101,此时反序列化操作立马终止并且报错:

    image-20210508222602074

  • 自定义的 privatereadObject() 方法可以被自动调用的原因:

    进入 ObjectStreamClass 类的最底层源码:

    image-20210508222740586

    可以发现,通过反射调用了类中定义的 private 私有方法

七、继承关系序列化问题

序列化是以正向递归的形式进行的,如果父类实现了序列化那么其子类都将被序列化;子类实现了序列化而父类没实现序列化,那么只有子类的属性会进行序列化,而父类的属性是不会进行序列化的

八、反序列化创建对象的方式

反序列化时获取对象需要调用对象输入流 ObjectInputStream 中的 readObject() 方法,进入此方法的源码,继续进入其调用的 readObject0 方法,再进入其调用的 readOrdinaryObject 方法:

image-20210509220307533

观察上述源码发现,在反序列化过程中,会判断待序列化类是否定义了 readResolve 方法,如果定义了此方法,则反序列化返回的对象是此方法返回的对象,否则返回新new出来的对象(这点应用在了单例模式中)

发表评论

表情:
评论列表 (有 0 条评论,79人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java序列(JAVA序列攻击)

    什么是java的序列化和反序列化? 1、什么是序列化?为什么要序列化? Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是只将字节序列转换成目标对象的过程

    相关 序列序列

    序列化: 对象的序列化主要有两种用途:   1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;   2) 在网络上传送对象的字节序列。   

    相关 Java序列序列

    目录 1:什么是序列化、反序列化? 2:序列化的用途? 3:序列化的n种方式 1:什么是序列化、反序列化? 把对象转换为字节序列的过程称为对象的序列化

    相关 java 序列序列

    一、什么是序列化与反序列化? > Java 序列化是指把 Java 对象转换为字节序列的过程; > Java 反序列化是指把字节序列恢复为 Java 对象的过程; 二、