Java 序列化深入分析

序列化机制介绍

??序列化是指把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对 象的状态以及相关的描述信息。客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。序列化机制的核心作用就是对象状态的 保存与重建。

Java序列化机制解析

??Java API提供了对序列化的支持,要实现对象的序列化和反序列化,基本上包括两个步骤:

1.声明对象具有可序列化的能力
2.通过Java API实现具体的序列化处理

??在Java语言中,声明对象具有可序列化的能力主要有两种方式:

其一,实现Serializable接口;
其二,实现Externalizable接口。

两者既有区别又有联系。

Serializable接口

??Serializable接口在JDK源码中定义如下:

/*
 * @author  unascribed
 * @see java.io.ObjectOutputStream
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutput
 * @see java.io.ObjectInput
 * @see java.io.Externalizable
 * @since   JDK1.1
 */
public interface Serializable {
}

??Java从JDK1.1开始支持对象的序列化机制,有上图的源码可知,Serializable接口没有声明任何方法,实现该接口的Java类不需要对任何方法提供实现(默认情况下,定制序列化时除外),**因此,该接口仅仅是一个mark interface(标记接口)”,实现该接口意味着告知JVM该对象可以序列化。**Java序列化机制要求所有具备序列化的对象必须实现该接口,否则是不能被序列化的,如果对于没有实现该接口的对象进行序列化时,Java API会抛出异常,无法进行序列化。

transient关键字

??瞬时变量,指的是被transient关键字修饰的变量,该关键字表示为瞬时的,即不做持久化处理的,以此来控制属性是否被包含进入序列化的字节流中。

??Serializable接 口提供了默认的序列化行为,在默认情况下,开发人员只需实现该接口,无需进行其他额外的操作,即可实现的对象的序列化。

默认只对对象中非静态的字段以及非瞬时的字段进行序列化,其他的字段是不允许被序列化的。

??这是因为,静态变量是类变量,属于整个类,并非专属于每个对象实例,因此,不序列化静态变量时合理的。瞬时变量,指的是被transient关键字修饰的变量,该关键字表示为瞬时的,即不做持久化处理的,以此来控制属性是否被包含进入序列化的字节流中。因此,在序列化时,排除transient关键字修饰的属性也是合理的。

??这种情况的具体表现就是,在序列化的有序字节流中没有保存不能被序列化的字段的状态,因此,在反序列化时,这些字段状态是不能被重建的。但是有一点需要注 意的是,经过反序列化后的对象,除了对可被序列化的字段状态进行重建之外,其他的没有被序列化的字段作为对象属性的一部分,也在对象重建时得以初始化。但 是这些字段的状态是不被保存的,重建后的这些属性仅仅是系统赋予的默认值,而非保存了对象序列化之前的状态。

实现Serializable接口除了提供默认的序列化方式之外,同样允许开发人员定制序列化,即通过实现以下相同签名的方法来实现:

序列化方法:

private void writeObject(java.io.ObjectOutputStream out) throws IOException

反序列化方法:

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

其中,writeObject方法用于定制序列化,readObject方法用于实现定制反序列化。

serialVersionUID

??每个实现Serializable接口的对象都有一个serialVersionUID,长整型,64位,唯一标示了该序列化对象。在类定义中,可以显示的定义该静态变量,也可以不定义。在不定义的情况下,Java编译器会隐式的生成该变量。强烈建议显示定义。那么,该变量有什么用途呢?反序列化兼容控制,serialVersionUID相同才能进行反序列化。例如:远程主机需要反序列化对象C,如果在本地和远程主机内的C对象持有的serialVersionUID不同,即使两个类其它部分完全一样,也是不能成功反序列化话的,会抛出异常。因此,如果对类做了修改,为了保证版本对序列化兼容,该变量的值保持不变。从另一个角度来讲,不期望不同版本的类对序列化兼容,则改变该变量值。

序列化保存的数据

??那么,到底哪些数据被序列化到了有序字节流中呢?字节流数据包含了non-static和non-transient类型的成员数据、类名、类签名、可序列化的基类以及类中引用的其他对象。

针对于父类,有几点原则:

1.     如果基类实现了Serializable接口,则其子类默认的是可序列化的,不必显示声明;
2.     如果基类没有实现Serializable接口,在反序列化时,会调用基类的无参构造方法来重建基类对象,只不过不会保留基类对象状态。

利用Serializable 实现序列化的实例。

public class User implements Serializable {

    private String userName;
    private String passWord;

    public User(String userName, String passWord) {
        this.userName = userName;
        this.passWord = passWord;
    }

    public String getUserName() {
        return userName;
    }

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

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    @Override
    public String toString() {
        return "User{" +
                "userName=‘" + userName + ‘\‘‘ +
                ", passWord=‘" + passWord + ‘\‘‘ +
                ‘}‘;
    }
}

测试代码

import java.io.*;

/**
 * @author bridge
 */
public class Client {

    public static void main(String[] args) {
        //构造一个可以序列化对象
        User user = new User("Bridge", "123456");

        //执行序列化
        try {
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("User.dat"));
            out.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //反序列化
        try {
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream("User.dat"));

            User userRecover = (User) in.readObject();
            System.out.println(userRecover);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果

User{userName=‘Bridge‘, passWord=‘123456‘}

Externalizable接口

Externalizable接口继承成自Serializable接口,实现该接口意味着对象本身完全掌控自己的序列化方式。该接口JDK源码如下:

    public interface Externalizable extends java.io.Serializable {

        void writeExternal(ObjectOutput out) throws IOException;

        void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
    }

Externalizable接口定义了两个方法:writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。write方法用于实现定制序列化,read方法用于实现定制反序列化,

定制序列化

??大部分情况下,我们期望对类似于密码等这样的敏感信息进行加密处理,以密文的形式在网 络间传输,增强数据的安全性。但是,通过我们上述的方式进行序列化,默认的处理方式是不能保证密码的密文传输的。因此,针对此类问题,我们必须能够定制对 象的序列化和反序列化过程,只有这样才能将我们的业务逻辑加入其中,以满足实际应用的需要。

基于Serializable接口的定制

??定制序列化和反序列化与上述序列化方式的不同在于:自定义类的实现。

首先,同样,自定义的类要实现Serializable接口,这是序列化处理的前提。不同的是,在定制序列化时,需要根据我们的实际需要,重写writeObject和readObject方法,完成序列化和反序列化的定制。示例代码如下:

User.java

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.*;

/**
 * @author bridge
 */
public class User implements Serializable {

    private String userName;
    private String passWord;

    public User(String userName, String passWord) {
        this.userName = userName;
        this.passWord = passWord;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        ObjectOutputStream.PutField putField = out.putFields();
        System.out.println("原密码为" + passWord);
        passWord = getBase64(passWord);//模拟加密
        putField.put("passWord", passWord);
        putField.put("userName", userName);
        out.writeFields();
        System.out.println("正在进行序列持久化");
    }

    private void readObject(ObjectInputStream in) throws IOException,
            ClassNotFoundException {
        ObjectInputStream.GetField getField = in.readFields();
        userName = (String) getField.get("userName", "");
        passWord = (String) getField.get("passWord", "");
        passWord = getFromBase64(passWord);//解密
        System.out.println("读取持久化对象");
    }

    // 加密
    public static String getBase64(String str) {
        byte[] b = null;
        String s = null;
        try {
            b = str.getBytes("utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        if (b != null) {
            s = new BASE64Encoder().encode(b);
        }
        return s;
    }

    // 解密
    public static String getFromBase64(String s) {
        byte[] b = null;
        String result = null;
        if (s != null) {
            BASE64Decoder decoder = new BASE64Decoder();
            try {
                b = decoder.decodeBuffer(s);
                result = new String(b, "utf-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    public String getUserName() {
        return userName;
    }

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

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    @Override
    public String toString() {
        return "User{" +
                "userName=‘" + userName + ‘\‘‘ +
                ", passWord=‘" + passWord + ‘\‘‘ +
                ‘}‘;
    }
}

测试类Client.java

import java.io.*;

/**
 * @author bridge
 */
public class Client {

    public static void main(String[] args) {
        //构造一个可以序列化对象
        User user = new User("Bridge", "123456");

        //执行序列化
        try {
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("User.dat"));
            out.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //反序列化
        try {
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream("User.dat"));

            User userRecover = (User) in.readObject();
            System.out.println(userRecover);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果

原密码为123456
正在进行序列持久化
读取持久化对象
User{userName=‘Bridge‘, passWord=‘123456‘}

说明:定制序列化过程中,序列化和反序列化读取信息的顺序要保持一致,否则会出现意想不到的后果。

基于Externalizable接口的定制

实现Extenalizable接口的类将完全由自己控制自身的序列化和反序列化。示例代码如下:

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.*;

/**
 * @author bridge
 */
public class User1 implements Externalizable {

    private String userName;
    private String passWord;

    /**
     * 必须定义无参构造函数
     */
    public User1() {

    }

    public User1(String userName, String passWord) {
        this.userName = userName;
        this.passWord = passWord;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {

        System.out.println("自定义序列化过程");
        out.writeObject(userName);
        out.writeObject(getBase64(passWord));
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("自定义反序列化过程");
        userName = (String) in.readObject();
        passWord = getFromBase64((String) in.readObject());
    }

    // 加密
    public static String getBase64(String str) {
        byte[] b = null;
        String s = null;
        try {
            b = str.getBytes("utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        if (b != null) {
            s = new BASE64Encoder().encode(b);
        }
        return s;
    }

    // 解密
    public static String getFromBase64(String s) {
        byte[] b = null;
        String result = null;
        if (s != null) {
            BASE64Decoder decoder = new BASE64Decoder();
            try {
                b = decoder.decodeBuffer(s);
                result = new String(b, "utf-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    public String getUserName() {
        return userName;
    }

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

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    @Override
    public String toString() {
        return "User{" +
                "userName=‘" + userName + ‘\‘‘ +
                ", passWord=‘" + passWord + ‘\‘‘ +
                ‘}‘;
    }

}

测试结果

自定义序列化过程
自定义反序列化过程
User{userName=‘Bridge‘, passWord=‘123456‘}

两种定制方法的区别

??特别注意, 利用Serializable序列化对象在反序列化时,对象完全以它保存下来的二进制位为基础恢复,不存在构建器调用。而对一个 Externalizable 对象,所有普通的默认构建行为都会发生(包括在字段定义时的初始化),而且会调用 readExternal() 。所有默认的构建行为都会进行,否则很难在自己的 Externalizable 对象中产生正确的行为,因此在利用Externalizable 接口实现序列化定制时,需要提供默认的无参构造方法,否则会抛出如下异常:

java.io.InvalidClassException: no valid constructor

序列化需要注意的问题

网络传输的安全性

??对象进行序列化之后转化成有序的字节流在网络上进行传输,如果通过默认的序列化方式, 则代码都是以明文的方式进行传输。这种情况下,部分字段的安全性是不能保障的,特别是像密码这样的安全敏感的信息。因此,如果您需要对部分字段信息进行特 殊的处理,那么应当选择定制对象的序列化方式,例如对密码等敏感信息进行加密处理。

类自身封装的安全性

??对对象进行序列化时,类中所定义的被private、final等 访问控制符所修饰的字段是直接忽略这些访问控制符而直接进行序列化的,因此,原本在本地定义的想要一次控制字段的访问权限的工作都是不起作用的。对于序列 化后的有序字节流来说一切都是可见的,而且是可重建的。这在一定程度上削弱了字段的安全性。因此,如果您需要特别处理这些信息,您可以选择相应的方式对这 些属性进行加密或者其他可行的处理,以尽量保持数据的安全性。

时间: 2024-10-05 05:01:46

Java 序列化深入分析的相关文章

深入分析java序列化

概念 先来点简单的概念: what?why? 什么是序列化?为什么要序列化? 答曰:将java对象转成字节序列,用以传输和保存 where? 使用场景是什么? 答曰:对象的传输:状态的备份,例如jvm的dump文件: 好了,不装*了,下面说的详细点.其实对象的序列化主要有两种用途: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中 在网络上传送对象的字节序列 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Sessio

Java序列化的作用

很多人都认为Java序列化的作用有以下两方面: 1) 把对象的字节序列永久地保存到硬盘上(通常存放在一个文件中): 2) 在网络上传送对象的字节序列. 可是我有一个疑问:第一个作用在什么情况会用到呢?第二个作用在什么情况会用到呢? 答: 归纳起来,就是把你的数据换个时间和/或换个地方,继续使用 换个时间,比如存盘 换个地方,比如网络传输 当然,实现"换个时间/地方用"的方式很多很多 正解...真正自己去序列化的还是比较少的,很多框架里面都做好了的..类似序列化功能的实现方式很多,不一定

Java 序列化与反序列化

1.什么是序列化?为什么要序列化? Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是只将字节序列转换成目标对象的过程. 我们都知道,在进行浏览器访问的时候,我们看到的文本.图片.音频.视频等都是通过二进制序列进行传输的,那么如果我们需要将Java对象进行传输的时候,是不是也应该先将对象进行序列化?答案是肯定的,我们需要先将Java对象进行序列化,然后通过网络,IO进行传输,当到达目的地之后,再进行反序列化获取到我们想要的对象,最后完成通信. 2.如何实现序列化 2.1.使用到JD

Java序列化(Serialization)

关于Java的序列化的文章在网上已经够多了,在这里写关于Java序列化的文章是对自己关于这方面的的一种总结,结合以前的开发经验与网上的资料,写了这篇文章,对自己是有着巩固记忆的作用,也希望能够对大家有一定帮助. 一.什么是序列化(Serialization)? 序列化是Java提供的一种机制,将对象转化成字节序列,在字节序列中保存了对象的数据.对象的类型的信息与存储在对象中的数据的类型.序列化实际上就是将保存对象的"状态",可以方便以后的程序使用或者通过网络传输到另一台主机使用.一般来

【总结】你所不知道的Java序列化

我们都知道,Java序列化可以让我们记录下运行时的对象状态(对象实例域的值),也就是我们经常说的对象持久化 .这个过程其实是非常复杂的,这里我们就好好理解一下Java的对象序列化. 1. 首先我们要搞清楚,Java对象序列化是将 对象的实例域数据( 包括private私有域) 进行持久化存储.而并非是将整个对象所属的类信息进行存储. 其实了解JVM的话,我们就能明白这一点了.实际上堆中所存储的对象包含了实例域数据值以及指向类信息的地址,而对象所属的类信息却存放在方法区中.当我们要对持久层数据反序

Hadoop序列化与Java序列化

序列化就是把内存中的对象的状态信息转换成字节序列,以便于存储(持久化)和网络传输 反序列化就是就将收到的字节序列或者是硬盘的持久化数据,转换成内存中的对象. 1.JDK的序列化 只要实现了serializable接口就能实现序列化与反序列化,一定要加上序列化版本ID serialVersionUID,这个是用来识别序列化的之前的类到底是哪一个.比如希望类的不同版本对序列化兼容,需要确保类的不同版本具有相同的serialVersionUID: Java序列化算法需要考虑: 将对象实例相关的类元数据

初探Java序列化(Serialization)

Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化Deserialization是一种将这些字节重建成一个对象的过程.[字节流的来回转换] Java中,一切都是对象,在分布式环境中经常需要将Object从这一端网络或设备传递到另一端.这就需要有一种可以在两端传输数据的协议.Java序列化机制就是为了解决这个问题而产生. 将对象状态转换成字节流之后,可以用java.io包中各种字节流的类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机.对象序

初探Java序列化(2)-writeObject/readObject

上一篇<初探Java序列化(Serialization)>给我们大体介绍了什么是序列化和反序列化,以及解析了一下序列化出来的文件.接着我们看看JDK具体如何序列化一个Object. 在序列化过程中,虚拟机会试图调用对象类里的writeObject() 和readObject(),进行用户自定义的序列化和反序列化,如果没有则调用ObjectOutputStream.defaultWriteObject() 和ObjectInputStream.defaultReadObject().同样,在Ob

Java序列化的理解与学习

1.什么是Java序列化 Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比 JVM的生命周期更长.但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象.Java对象序列 化就能够帮助我们实现该功能. 必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量.由此可知,对象序列化不会关注类中的静态变量. 所谓序列化其实就是将程序中的数据(对