序列化&反序列化

目录

1、基本概念

2、使用场景

3、JDK中序列化和反序列化API

3.1 基本原理

3.2 示例

(1)类未实现Serializable接口,进行序列化的范例

(2)类实现Serializable接口,进行序列化和反序列化的范例

4、serialVersionUID的作用

4.1 作用

4.2 示例

5、transient关键字的作用

5.1 作用

5.2 示例

(1)transient属性不被序列化

(2)static属性不被序列化

6、Externalizable接口的作用

6.1 作用

6.2 示例

7、序列化需要注意的问题

8、反序列化漏洞


1、基本概念

  • 序列化:指把java对象转换为字节序列的过程;

  • 反序列化:指把字节序列恢复为java对象的过程;

  • 序列化的实体是个对象,结果也是个对象,并非是格式化文本,我们在记事本里看到的文本记录,其实不是对象序列化的结果,而是对象输出的格式化文本,真正的序列化对象是看不懂的

  • 序列化的结果是个只有JAVA虚拟机认识的文件,人不参与,只是用于保存对象或传输。

2、使用场景

当你想从一个jvm中调用另一个jvm的对象时,你就可以考虑使用序列化了。序列化的作用就是为了不同jvm之间共享实例对象的一种解决方案。

对象的序列化主要有两种用途:
(1)把对象的字节序列保存到硬盘上,通常存放在一个文件中:在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中;
(2)在网络上传送对象的字节序列:当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象;

3、JDK中序列化和反序列化API

3.1 基本原理

  • java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;

  • java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回;

  • 只有实现了Serializable接口或Externalizable接口的类的对象才能被序列化;

  • 如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。

  • 对象序列化包括如下步骤:
    (1)创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流,字节数组输出流;
    (2)通过对象输出流的writeObject()方法写对象;

  • 对象反序列化的步骤如下:
    (1)创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流,字节数组输入流;
    (2)通过对象输入流的readObject()方法读取对象;

3.2 示例

(1)类未实现Serializable接口,进行序列化的范例

定义一个未实现Serializable的类:User

public class User {
    private Integer userId;
    private String userName;

    public Integer getUserId() {
        return userId;
    }

    @Override
    public String toString() {
        return "User [userId=" + userId + ", userName=" + userName + "]";
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

序列化测试:

public class Test {
    public static void main(String[] args) throws Exception {
        serialize();
        User o = (User) deSerialize();
        System.out.println(o.toString());

    }

    public static void serialize() throws IOException {
        User u = new User();
        u.setUserId(1234567);
        u.setUserName("zhangsan");
        FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectOutputStream os = new ObjectOutputStream(fo);
        os.writeObject(u);
    }

    public static Object deSerialize() throws IOException, ClassNotFoundException {
        FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectInputStream oi = new ObjectInputStream(fi);
        return oi.readObject();
    }
}

运行结果如下:

序列化&反序列化

(2)类实现Serializable接口,进行序列化和反序列化的范例

令上述User类实现Serializable接口:

public class User implements Serializable {
    private Integer userId;
    private String userName;

    public Integer getUserId() {
        return userId;
    }

    @Override
    public String toString() {
        return "User [userId=" + userId + ", userName=" + userName + "]";
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

运行上述序列化测试,结果如下(运行成功,本地相应路径下也生成了一个a.txt文件):

User [userId=1234567, userName=zhangsan]

 

4、serialVersionUID的作用

4.1 作用

        serialVersionUID作用:序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。有两种生成方式:

(1)一个是默认的1L,比如:private static final long serialVersionUID = 1L;

(2)一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long  serialVersionUID = xxxxL;

4.2 示例

为上述User类添加serialVersionUID:

public class User implements Serializable {
    private static final long serialVersionUID = 111L;
    private Integer userId;
    private String userName;

    public Integer getUserId() {
        return userId;
    }

    @Override
    public String toString() {
        return "User [userId=" + userId + ", userName=" + userName + "]";
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

执行上述测试,运行如下(运行成功,本地本地相应路径下也生成了一个a.txt文件):

User [userId=1234567, userName=zhangsan]

改变上述User类中的serialVersionUID:

public class User implements Serializable {
    private static final long serialVersionUID = 222L;
    private Integer userId;
    private String userName;

    public Integer getUserId() {
        return userId;
    }

    @Override
    public String toString() {
        return "User [userId=" + userId + ", userName=" + userName + "]";
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        //serialize();
        User o = (User) deSerialize();
        System.out.println(o.toString());
    }

    public static void serialize() throws IOException {
        User u = new User();
        u.setUserId(1234567);
        u.setUserName("zhangsan");
        FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectOutputStream os = new ObjectOutputStream(fo);
        os.writeObject(u);
    }

    public static Object deSerialize() throws IOException, ClassNotFoundException {
        FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectInputStream oi = new ObjectInputStream(fi);
        return oi.readObject();
    }
}

运行结果如下:

序列化&反序列化

        class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID。因此强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。

5、transient关键字的作用

5.1 作用

  • 在实际开发过程中,我们常常会遇到这样的问题:类的有些属性需要序列化,而其他属性不需要被序列化。比如,用户有一些敏感信息(如mima,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化;

  • java 的transient关键字为我们提供了便利,只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中;

5.2 示例

(1)transient属性不被序列化

为User类新增家庭住址属性,加上transient关键字:

public class User implements Serializable {
    private static final long serialVersionUID = 222L;
    private Integer userId;
    private String userName;
    private transient String address;

    public Integer getUserId() {
        return userId;
    }

    @Override
    public String toString() {
        return "User [userId=" + userId + ", userName=" + userName + ", address =" + address + "]";
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

设置家庭住址,执行序列化&反序列化测试:

public class Test {
    public static void main(String[] args) throws Exception {
        serialize();
        User o = (User) deSerialize();
        System.out.println(o.toString());
    }

    public static void serialize() throws IOException {
        User u = new User();
        u.setUserId(1234567);
        u.setUserName("zhangsan");
        u.setAddress("南湖南路999号");
        FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectOutputStream os = new ObjectOutputStream(fo);
        os.writeObject(u);
    }

    public static Object deSerialize() throws IOException, ClassNotFoundException {
        FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectInputStream oi = new ObjectInputStream(fi);
        return oi.readObject();
    }
}

 结果如下(地址属性不被序列化):

User [userId=1234567, userName=zhangsan, address =null]

(2)static属性不被序列化

为User类新增组别属性,加上static关键字:

public class User implements Serializable {
    private static final long serialVersionUID = 222L;
    private Integer userId;
    private String userName;
    private transient String address;
    private static Integer groupId;

    public static Integer getGroupId() {
        return groupId;
    }

    public static void setGroupId(Integer groupId) {
        User.groupId = groupId;
    }

    public Integer getUserId() {
        return userId;
    }

    @Override
    public String toString() {
        return "User [userId=" + userId + ", userName=" + userName + ", address =" + address + ", groupId =" + groupId + "]";
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

进行反序列化测试(执行反序列化之前已执行过serialize()序列化操作):

public class Test {
    public static void main(String[] args) throws Exception {
        //serialize();
        User o = (User) deSerialize();
        System.out.println(o.toString());
    }

    public static void serialize() throws IOException {
        User u = new User();
        u.setUserId(1234567);
        u.setUserName("zhangsan");
        u.setAddress("南湖南路999号");
        User.setGroupId(8);
        FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectOutputStream os = new ObjectOutputStream(fo);
        os.writeObject(u);
    }

    public static Object deSerialize() throws IOException, ClassNotFoundException {
        FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectInputStream oi = new ObjectInputStream(fi);
        return oi.readObject();
    }
}

结果如下(组别属性未被序列化):

User [userId=1234567, userName=zhangsan, address =null, groupId =null]

若同时进行序列化&反序列化测试:

public class Test {
    public static void main(String[] args) throws Exception {
        serialize();
        User o = (User) deSerialize();
        System.out.println(o.toString());
    }

    public static void serialize() throws IOException {
        User u = new User();
        u.setUserId(1234567);
        u.setUserName("zhangsan");
        u.setAddress("南湖南路999号");
        User.setGroupId(8);
        FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectOutputStream os = new ObjectOutputStream(fo);
        os.writeObject(u);
    }

    public static Object deSerialize() throws IOException, ClassNotFoundException {
        FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectInputStream oi = new ObjectInputStream(fi);
        return oi.readObject();
    }
}

结果如下(组别属性也被序列化):

User [userId=1234567, userName=zhangsan, address =null, groupId =8]

结果发现,组别属性竟然有值。这时因为这个值并不是从文件中反序列化读出来的,是从当前JVM中对应static变量的值。

6、Externalizable接口的作用

6.1 作用

Externalizable是Serializable接口的子类,如果不希望序列化那么多属性,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性;

6.2 示例

定义User类实现Externalizable接口,重写writeExternal和readExternal方法:

public class User implements Externalizable {
    private static final long serialVersionUID = 222L;
    private Integer userId;
    private String userName;
    private Integer sex;

    public Integer getUserId() {
        return userId;
    }

    @Override
    public String toString() {
        return "User [userId=" + userId + ", userName=" + userName + ", sex =" + sex + "]";
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(userId);
        out.writeObject(sex);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        userId = (Integer) in.readObject();
        sex = (Integer) in.readObject();
    }
}

序列化测试:

public class Test {
    public static void main(String[] args) throws Exception {
        serialize();
        User o = (User) deSerialize();
        System.out.println(o.toString());
    }

    public static void serialize() throws IOException {
        User u = new User();
        u.setUserId(1234567);
        u.setUserName("zhangsan");
        u.setSex(1);
        FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectOutputStream os = new ObjectOutputStream(fo);
        os.writeObject(u);
    }

    public static Object deSerialize() throws IOException, ClassNotFoundException {
        FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt"));
        ObjectInputStream oi = new ObjectInputStream(fi);
        return oi.readObject();
    }
}

测试结果如下:

User [userId=1234567, userName=null, sex =1]

7、序列化需要注意的问题

1、序列化时,只对对象的状态进行保存,而不管对象的方法;

2、当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

3、当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;

4、若一个对象拥有private,public等field,在序列化进行传输的过程中,这个对象的private等域是不受保护的。所以出于安全方面的考虑,并非所有的对象都可以序列化;

8、反序列化漏洞

反序列化漏洞不是java独有的其他语言也会有,但都一样是在反序列化时执行了注入的代码。

详细原理戳这里

上一篇:SpringSecurity+JWT 登录认证+鉴权(未完)


下一篇:Android架构组件-App架构指南,2021我是如何拿到小米、京东、字节的offer