java.io.Serializable

一个标示性接口,接口中没有定义任何的方法或字段,仅用于标示可序列化的语义。
序列化时只对对象的状态进行保存,而无论对象的方法;序列化前和序列化后的对象的关系为深拷贝java

  •  Serialization(序列化)将对象转为字节的过程:
    首先要建立OutputStream(FileOutputStream、ByteArrayOutputStream等),而后将这些OutputStream封装在一个ObjectOutputStream中,调用writeObject()方法就能够将对象序列化,并将其发送给OutputStream。对象的序列化是基于字节,不能使用Reader和Writer等基于字符的层次结构)
  • Deserialization(反序列化)是将字节重建成一个对象的过程:
    将一个InputStream(FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,而后调用readObject()。

序列化 ID

序列化使用一个 hash,该 hash 是根据给定源文件中几乎全部东西(类路径、 方法名称、字段名称、字段类型、访问修改方法等) 经过运行 JDK serialver 命令计算出的,反序列化时将该 hash 值与序列化流中的 hash 值相比较,serialVersionUID 相同才可以被序列化算法

若是serialVersionUID类中没有指定,JVM将从新计算出serialVersionUID; 若是类几乎全部东西都相同仍然可以反序列化,不然不能。函数

java.io.InvalidClassException: com.noob.Person; local class incompatible: stream classdesc serialVersionUID = 7763748706987261198, local class serialVersionUID = 1279018472691830503
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1829)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1986)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at com.noob.TestSerializable.read(TestSerializable.java:41)
	at com.noob.TestSerializable.main(TestSerializable.java:18)

若是在反序列化前修改了类路径(或者JVM没有加载到原有的类)报错:测试

eg. 修改了Person的类路径 由 com.noob 改成 com.noob.athis

java.lang.ClassNotFoundException: com.noob.Person
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:677)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1819)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1986)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at com.noob.a.TestSerializable.read(TestSerializable.java:41)
	at com.noob.a.TestSerializable.main(TestSerializable.java:18)

反序列化对象类型是序列化对象的父类或自己

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import lombok.Getter;
import lombok.Setter;


public class TestSerializable {

    public static void main(String[] args) {
        try {
            /* 深复制 */
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            ObjectOutputStream readIn = new ObjectOutputStream(bo);
            A testA = new A("a");
            testA.setB("b");
            testA.setC("c");
            testA.setD("d");

            readIn.writeObject(testA);
            ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
            ObjectInputStream writeOut = new ObjectInputStream(bi);
            B b = B.class.cast(writeOut.readObject()); // 反序列化对象类型是序列化对象的父类或自己
            System.out.println(b);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

@Getter
@Setter
class A extends B implements Serializable {


    public A(String a) {
		super(a);
	}
	private static final long serialVersionUID = 1L;
    private String            d;
    private String a, b, c;

}

@Getter
@Setter
class B implements Serializable {
    private String a, b, c;

    public B(String a) {
        this.a = a;
    }
}

测试发现:加密

  • 在反序列化时,用的还是序列化时的对象类型
  • 没有无参的构造方法,也能够(反)序列化
  • 若是A、B 两个类没有父子关系,程序异常:

父类的序列化与 Transient 关键字

  • 当一个父类实现序列化,子类自动实现序列化,不须要显式实现Serializable接口
  • 若是父类不实现序列化接口则须要有默认的无参的构造函数!不然子类的序列化会出错

在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。因此反序列化时,为了构造父对象,只能调用父类的无参构造函数做为默认的父对象。所以当取父对象的变量值时,它的值是调用父类无参构造函数后的值。spa

若是考虑到这种序列化的状况,在父类无参构造函数中对变量进行初始化,不然的话,父类变量值都是默认声明的值。.net

Transient 关键字的做用是控制变量的序列化,在变量声明前加上该关键字,能够阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值
静态变量、成员方法、声明transient的变量 都不能被序列化和反序列化!3d

案例分析

eg. 使用 Transient 关键字可使得字段不被序列化,那么还有别的方法吗?
   根据父类对象序列化的规则,咱们能够将不须要被序列化的字段抽取出来放到父类中,子类实现 Serializable 接口,父类不实现,根据父类序列化规则,父类的字段数据将不被序列化,造成类图:
     图 2. 案例程序类图
code

  上图中能够看出,attr一、attr二、attr三、attr5 都不会被序列化,放在父类中的好处在于当有另一个 Child 类时,attr一、attr二、attr3 依然不会被序列化,不用重复抒写 transient,代码简洁。

序列化容许重构

即便代码有必定的变化,可是serialVersionUID相同,仍然能够被反序列化当出现新字段时会被设为缺省值。

eg. 将原有的Person写入到文件中。再修改Person类:staS改成非 static,firstName 改成非 transient , 增长属性addField。(static与transient 有或无 可互相转换,经测试都不能被正确序列化和反序列化)

package com.noob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import com.zhongan.fcp.pre.allin.common.utils.JSONUtils;

public class TestSerializable {
    public static void main(String[] args) {
        /* write(); */
        read();

    }

    private static void write() {
       /* try {
            Person ted1 = new Person("firstName", "lastName", 39);

            FileOutputStream fos = new FileOutputStream("tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(ted1);
            oos.close();
            System.out.println("---serialize obj end.----");
        } catch (Exception ex) {
            ex.printStackTrace();
        }*/
    }

    private static void read() {
        try {
            FileInputStream fis = new FileInputStream("D:/tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Person ted2 = (Person) ois.readObject();
            ois.close();
            System.out.println(JSONUtils.toFormatJsonString(ted2));
            // Clean up the file
            new File("tempdata.ser").delete();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements java.io.Serializable {
    private static final long serialVersionUID = -5941751315700344441L;
    private/**static**/String    staS             = "xxx"; // static 改成非 static 

    private/**transient**/String firstName;               // transient 改成非 transient 
    private String                lastName;
    private int                   age;
    private String                addField;                // 增长属性

}

对象引用的序列化

一个类要能被序列化,该类中的全部引用对象也必须是能够被序列化的。

不然整个序列化操做将会失败,而且会抛出一个NotSerializableException,除非将不可序列化的引用标记为transient

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -5941751315700344441L;

    private static String     staS             = "xxx";                // static 改成非 static 

    private transient String  firstName;                               // transient 改成非 transient 
    private String            lastName;
    private int               age;
    private final Attribute   attribute        = new Attribute();

}

@Data
class Attribute {
    private String lock;
}
java.io.NotSerializableException: com.noob.Attribute
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.noob.TestSerializable.write(TestSerializable.java:26)
	at com.noob.TestSerializable.main(TestSerializable.java:17)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.noob.Attribute
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1539)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2231)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2155)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2013)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at com.noob.TestSerializable.read(TestSerializable.java:36)
	at com.noob.TestSerializable.main(TestSerializable.java:18)
Caused by: java.io.NotSerializableException: com.noob.Attribute
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.noob.TestSerializable.write(TestSerializable.java:26)
	at com.noob.TestSerializable.main(TestSerializable.java:17)

序列化对象的引用关系

若是使用序列化机制向文件中写入了多个对象,在反序列化时,须要按实际写入的顺序读取。

JAVA的序列化机制采用了一种特殊的算法来保证序列化对象的关系:

全部保存到磁盘中的对象都有一个序列化编号。当程序试图序列化一个对象时,会先检查该对象是否已经被序列化过,只有该对象(在本次虚拟机中)从未被序列化,系统才会将该对象转换成字节序列并输出若是对象已经被序列化,程序将直接输出一个序列化编号,而不是从新序列化。

package com.noob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

public class TestSerializable {
    public static void main(String[] args) {
        try {
            Person person1 = new Person(1000);
            FileOutputStream fos = new FileOutputStream("D:/tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person1);
            oos.flush(); //可选
            System.out.println(String.format("第一次将person1写入后的长度: %s", new File("D:/tempdata.ser").length()));
            person1.setAge(5555); //修改属性值
            oos.writeObject(person1);
            System.out.println(String.format("再次将person1写入后的长度: %s", new File("D:/tempdata.ser").length()));
            
            Person person2 = new Person(1000);
            oos.writeObject(person2);
            oos.close();
            System.out.println(String.format("初始化新person2写入后的长度:%s ", new File("D:/tempdata.ser").length()));
            
            
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/tempdata.ser"));
            System.out.println("---deserialization obj begin.----");

            Person deserialization_person1 = (Person) ois.readObject();
            System.out.println("deserialization_person1的age: " + deserialization_person1.getAge());
            System.out.println("deserialization_person1的内存: " + deserialization_person1);
            
            Person deserialization_person2 = (Person) ois.readObject();
            System.out.println("deserialization_person2的age: " + deserialization_person2.getAge());
            System.out.println("deserialization_person2的内存: " + deserialization_person2);
            System.out.println(String.format("deserialization_person1与deserialization_person2是否一致:%s ",
                    deserialization_person1 == deserialization_person2));
            
            Person deserialization_person3 = (Person) ois.readObject();
            System.out.println("deserialization_person3的内存: " + deserialization_person3);
            System.out.println(String.format("deserialization_person1与deserialization_person3是否一致:%s ",
                    deserialization_person1 == deserialization_person3));

        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }
}

@Getter
@Setter
@AllArgsConstructor
class Person implements java.io.Serializable {
    /**
 * 
 */
    private static final long serialVersionUID = -5941751315700344441L;

    private int               age;

}

测试结果发现:

  1. 即便修改了age的属性值,可是deserialization_person2反序列化时仍然是原初始化值。
  2. 第二次写入对象时文件只增长了 5 字节(存储新增引用和一些控制信息),而且反序列化时恢复引用关系(地址), 两个对象是相等的。 

案例分析

流只能被读取一次!

eg. 若是序列化一个对象,反序列化时屡次readObject,报错

try {
            Person person = new Person(1000);
            FileOutputStream fos = new FileOutputStream("D:/tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person);
            oos.close();
            FileInputStream fis = new FileInputStream("D:/tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Person person2 = (Person) ois.readObject();
            Person person3 = (Person) ois.readObject();

            ois.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

eg. 有两个Teacher对象,它们的Student实例变量都引用了同一个Person对象,并且该Person对象还另一个引用变量引用它。以下图所示: 

                                            

 这里有三个对象per、t一、t2,若是都被序列化,会存在这样一个问题,在序列化t1的时候,会隐式的序列化person对象。在序列化t2的时候,也会隐式的序列化person对象。在序列化per的时候,会显式的序列化person对象。因此在反序列化的时候,会获得三个person对象,这样就会形成t一、t2所引用的person对象不是同一个。显然,这并不符合图中所展现的关系,也违背了java序列化的初衷。

package com.noob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import lombok.AllArgsConstructor;
import lombok.Getter;

public class TestSerializable {
    public static void main(String[] args) {
        write();
        read();

    }

    private static void write() {
        try {
            Person person = new Person(1000);
            Student student = new Student("孙悟空", person);
            Teacher teacher1 = new Teacher("唐僧", student);
            Teacher teacher2 = new Teacher("菩提老祖", student);

            FileOutputStream fos = new FileOutputStream("D:/tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person);
            oos.flush(); // 可选
            oos.writeObject(student);
            oos.flush();
            oos.writeObject(teacher1);
            oos.flush();
            oos.writeObject(teacher2);
            oos.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    private static void read() {
        try {
            FileInputStream fis = new FileInputStream("D:/tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            System.out.println("---deserialization obj begin.----");

            Person person = (Person) ois.readObject();
            System.out.println("person的内存: " + person);
            Student student = (Student) ois.readObject();
            System.out.println("student中person内存: " + student.getPerson());
            System.out.println(String.format("student中person与直接反序列化的person是否一致:%s", student.getPerson() == person));
            System.out.println("-------------------------------------------");
            System.out.println("student的内存: " + student);

            Teacher teacher1 = (Teacher) ois.readObject();
            System.out.println("teacher1中student内存: " + teacher1.getStudent());
            System.out
                    .println(String.format("teacher1中student与直接反序列化的student是否一致:%s", teacher1.getStudent() == student));

            Teacher teacher2 = (Teacher) ois.readObject();
            System.out.println("teacher2中student内存: " + teacher2.getStudent());
            System.out
                    .println(String.format("teacher2中student与直接反序列化的student是否一致:%s", teacher2.getStudent() == student));

            ois.close();
            // Clean up the file
            new File("tempdata.ser").delete();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

/**
 * 此处没有getter/setter 佐证序列和反序列化与此无关
 */
@AllArgsConstructor
class Person implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -5941751315700344441L;

    private int               age;

}

@Getter
@AllArgsConstructor
class Student implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String            name;
    private Person            person;
}

@Getter
@AllArgsConstructor
class Teacher implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String            name;
    private Student           student;
}

测试结论: 在反序列化后仍旧会保持与序列化前对象间的引用关系!

敏感字段加密

在序列化过程当中,虚拟机会试图调用对象类里的 writeObject readObject 方法,进行用户自定义的序列化和反序列化,若是没有这样的方法,则默认调用是 ObjectOutputStream defaultWriteObject 方法以及 ObjectInputStream defaultReadObject 法。

package com.noob;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream.PutField;

public class Test implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private String            password         = "origin_password";

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    private void writeObject(ObjectOutputStream out) {
        try {
            PutField putFields = out.putFields();
            System.out.println("原密码:" + password);
            password = "encryption";//此处能够经过公钥进行加密
            putFields.put("password", password);
            System.out.println("加密后的密码" + password);
            out.writeFields();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void readObject(ObjectInputStream in) {
        try {
            GetField readFields = in.readFields();
            Object object = readFields.get("password", "");
            System.out.println("要解密的字符串:" + object.toString());
            password = "origin_password";//此处能够经过私钥进行解密
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:/result.obj"));
            out.writeObject(new Test());
            out.close();

            ObjectInputStream oin = new ObjectInputStream(new FileInputStream("D:/result.obj"));
            Test t = (Test) oin.readObject();
            System.out.println("解密后的字符串:" + t.getPassword());
            oin.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
相关文章
相关标签/搜索