Java 序列化/反序列化

在Java中实现对象反序列化非常简单,实现java.io.Serializable(内部序列化)java.io.Externalizable(外部序列化)接口即可被序列化(Externalizable接口只是实现了java.io.Serializable接口)。

反序列化类对象时有如下限制:

  1. 被反序列化的类必须存在。
  2. serialVersionUID值必须一致。

除此之外,反序列化类对象是不会调用该类构造方法的,因为在反序列化创建类实例时使用了sun.reflect.ReflectionFactory.newConstructorForSerialization创建了一个反序列化专用的Constructor(反射构造方法对象),使用这个特殊的Constructor可以绕过构造方法创建类实例(前面章节讲sun.misc.Unsafe 的时候我们提到了使用allocateInstance方法也可以实现绕过构造方法创建类实例)。

使用反序列化方式创建类实例代码片段:

  1. package com.anbai.sec.serializes;
  2. import sun.reflect.ReflectionFactory;
  3. import java.lang.reflect.Constructor;
  4. /**
  5. * 使用反序列化方式在不调用类构造方法的情况下创建类实例
  6. * Creator: yz
  7. * Date: 2019/12/20
  8. */
  9. public class ReflectionFactoryTest {
  10. public static void main(String[] args) {
  11. try {
  12. // 获取sun.reflect.ReflectionFactory对象
  13. ReflectionFactory factory = ReflectionFactory.getReflectionFactory();
  14. // 使用反序列化方式获取DeserializationTest类的构造方法
  15. Constructor constructor = factory.newConstructorForSerialization(
  16. DeserializationTest.class, Object.class.getConstructor()
  17. );
  18. // 实例化DeserializationTest对象
  19. System.out.println(constructor.newInstance());
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }

程序运行结果:

  1. com.anbai.sec.serializes.DeserializationTest@2b650cea

具体细节可参考 不用构造方法也能创建对象

ObjectInputStream、ObjectOutputStream

java.io.ObjectOutputStream类最核心的方法是writeObject方法,即序列化类对象。

java.io.ObjectInputStream类最核心的功能是readObject方法,即反序列化类对象。

所以,只需借助ObjectInputStreamObjectOutputStream类我们就可以实现类的序列化和反序列化功能了。

java.io.Serializable

java.io.Serializable是一个空的接口,我们不需要实现java.io.Serializable的任何方法,代码如下:

  1. public interface Serializable {
  2. }

您可能会好奇我们实现一个空接口有什么意义?其实实现java.io.Serializable接口仅仅只用于标识这个类可序列化。实现了java.io.Serializable接口的类原则上都需要生产一个serialVersionUID常量,反序列化时如果双方的serialVersionUID不一致会导致InvalidClassException 异常。如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID值。

DeserializationTest.java测试代码如下:

  1. package com.anbai.sec.serializes;
  2. import java.io.*;
  3. import java.util.Arrays;
  4. /**
  5. * Creator: yz
  6. * Date: 2019/12/15
  7. */
  8. public class DeserializationTest implements Serializable {
  9. private String username;
  10. private String email;
  11. // 省去get/set方法....
  12. public static void main(String[] args) {
  13. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  14. try {
  15. // 创建DeserializationTest类,并类设置属性值
  16. DeserializationTest t = new DeserializationTest();
  17. t.setUsername("yz");
  18. t.setEmail("admin@javaweb.org");
  19. // 创建Java对象序列化输出流对象
  20. ObjectOutputStream out = new ObjectOutputStream(baos);
  21. // 序列化DeserializationTest类
  22. out.writeObject(t);
  23. out.flush();
  24. out.close();
  25. // 打印DeserializationTest类序列化以后的字节数组,我们可以将其存储到文件中或者通过Socket发送到远程服务地址
  26. System.out.println("DeserializationTest类序列化后的字节数组:" + Arrays.toString(baos.toByteArray()));
  27. // 利用DeserializationTest类生成的二进制数组创建二进制输入流对象用于反序列化操作
  28. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  29. // 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
  30. ObjectInputStream in = new ObjectInputStream(bais);
  31. // 反序列化输入流数据为DeserializationTest对象
  32. DeserializationTest test = (DeserializationTest) in.readObject();
  33. System.out.println("用户名:" + test.getUsername() + ",邮箱:" + test.getEmail());
  34. // 关闭ObjectInputStream输入流
  35. in.close();
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. } catch (ClassNotFoundException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }

程序执行结果如下:

  1. DeserializationTest类序列化后的字节数组:[-84, -19, 0, 5, 115, 114, 0, 44, 99, 111, 109, 46, 97, 110, 98, 97, 105, 46, 115, 101, 99, 46, 115, 101, 114, 105, 97, 108, 105, 122, 101, 115, 46, 68, 101, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 84, 101, 115, 116, 74, 36, 49, 16, -110, 39, 13, 76, 2, 0, 2, 76, 0, 5, 101, 109, 97, 105, 108, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 8, 117, 115, 101, 114, 110, 97, 109, 101, 113, 0, 126, 0, 1, 120, 112, 116, 0, 17, 97, 100, 109, 105, 110, 64, 106, 97, 118, 97, 119, 101, 98, 46, 111, 114, 103, 116, 0, 2, 121, 122]
  2. 用户名:yz,邮箱:admin@javaweb.org

核心逻辑其实就是使用ObjectOutputStream类的writeObject方法序列化DeserializationTest类,使用ObjectInputStream类的readObject方法反序列化DeserializationTest类而已。

简化后的代码片段如下:

  1. // 序列化DeserializationTest类
  2. ObjectOutputStream out = new ObjectOutputStream(baos);
  3. out.writeObject(t);
  4. // 反序列化输入流数据为DeserializationTest对象
  5. ObjectInputStream in = new ObjectInputStream(bais);
  6. DeserializationTest test = (DeserializationTest) in.readObject();

ObjectOutputStream序列化类对象的主要流程是首先判断序列化的类是否重写了writeObject方法,如果重写了就调用序列化对象自身的writeObject方法序列化,序列化时会先写入类名信息,其次是写入成员变量信息(通过反射获取所有不包含被transient修饰的变量和值)。

java.io.Externalizable

java.io.Externalizablejava.io.Serializable几乎一样,只是java.io.Externalizable接口定义了writeExternalreadExternal方法需要序列化和反序列化的类实现,其余的和java.io.Serializable并无差别。

java.io.Externalizable.java:

  1. public interface Externalizable extends java.io.Serializable {
  2. void writeExternal(ObjectOutput out) throws IOException;
  3. void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
  4. }

ExternalizableTest.java测试代码如下:

  1. package com.anbai.sec.serializes;
  2. import java.io.*;
  3. import java.util.Arrays;
  4. /**
  5. * Creator: yz
  6. * Date: 2019/12/15
  7. */
  8. public class ExternalizableTest implements java.io.Externalizable {
  9. private String username;
  10. private String email;
  11. // 省去get/set方法....
  12. @Override
  13. public void writeExternal(ObjectOutput out) throws IOException {
  14. out.writeObject(username);
  15. out.writeObject(email);
  16. }
  17. @Override
  18. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
  19. this.username = (String) in.readObject();
  20. this.email = (String) in.readObject();
  21. }
  22. public static void main(String[] args) {
  23. // 省去测试代码,因为和DeserializationTest一样...
  24. }
  25. }

程序执行结果如下:

  1. ExternalizableTest类序列化后的字节数组:[-84, -19, 0, 5, 115, 114, 0, 43, 99, 111, 109, 46, 97, 110, 98, 97, 105, 46, 115, 101, 99, 46, 115, 101, 114, 105, 97, 108, 105, 122, 101, 115, 46, 69, 120, 116, 101, 114, 110, 97, 108, 105, 122, 97, 98, 108, 101, 84, 101, 115, 116, -122, 124, 92, -120, -52, 73, -100, 6, 12, 0, 0, 120, 112, 116, 0, 2, 121, 122, 116, 0, 17, 97, 100, 109, 105, 110, 64, 106, 97, 118, 97, 119, 101, 98, 46, 111, 114, 103, 120]
  2. ExternalizableTest类反序列化后的字符串:��sr+com.anbai.sec.serializes.ExternalizableTest�|\�I� xptyztadmin@javaweb.orgx
  3. 用户名:yz,邮箱:admin@javaweb.org

鉴于两者之间没有多大差别,这里就不再赘述。

自定义序列化(writeObject)和反序列化(readObject)

实现了java.io.Serializable接口的类还可以定义如下方法(反序列化魔术方法)将会在类序列化和反序列化过程中调用:

  1. private void writeObject(ObjectOutputStream oos),自定义序列化。
  2. private void readObject(ObjectInputStream ois),自定义反序列化。
  3. private void readObjectNoData()
  4. protected Object writeReplace(),写入时替换对象。
  5. protected Object readResolve()

具体的方法名定义在java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>)方法有详细的声明。

序列化时可自定义的方法示例代码:

  1. public class DeserializationTest implements Serializable {
  2. /**
  3. * 自定义反序列化类对象
  4. *
  5. * @param ois 反序列化输入流对象
  6. * @throws IOException IO异常
  7. * @throws ClassNotFoundException 类未找到异常
  8. */
  9. private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
  10. System.out.println("readObject...");
  11. // 调用ObjectInputStream默认反序列化方法
  12. ois.defaultReadObject();
  13. // 省去调用自定义反序列化逻辑...
  14. }
  15. /**
  16. * 自定义序列化类对象
  17. *
  18. * @param oos 序列化输出流对象
  19. * @throws IOException IO异常
  20. */
  21. private void writeObject(ObjectOutputStream oos) throws IOException {
  22. oos.defaultWriteObject();
  23. System.out.println("writeObject...");
  24. // 省去调用自定义序列化逻辑...
  25. }
  26. private void readObjectNoData() {
  27. System.out.println("readObjectNoData...");
  28. }
  29. /**
  30. * 写入时替换对象
  31. *
  32. * @return 替换后的对象
  33. */
  34. protected Object writeReplace() {
  35. System.out.println("writeReplace....");
  36. return null;
  37. }
  38. protected Object readResolve() {
  39. System.out.println("readResolve....");
  40. return null;
  41. }
  42. }

当我们序列化DeserializationTest类时,会自动调用(反射)该类的writeObject(ObjectOutputStream oos)方法,反序列化时候也会自动调用readObject(ObjectInputStream)方法,也就是说我们可以通过在需要序列化/反序列化的类中定义readObjectwriteObject方法从而实现自定义的序列化和反序列化操作,和当然前提是被序列化的类必须有此方法且方法的修饰符必须是private