1. 概述

在这个快速教程中,我们将展示如何在Java中验证可序列化对象。

2. 序列化与反序列化

序列化是指将一个对象的状态转换为字节流的过程。序列化后的对象主要在Hibernate、RMI、JPA、EJB和JMS等技术中使用。相反,反序列化是将字节流用于在内存中重建实际的Java对象的过程,通常用于持久化对象。

3. 序列化验证

我们可以使用多种方法来验证序列化。让我们来看看一些方法。

3.1. 通过implements验证序列化

检查一个对象是否是java.io.Serializablejava.io.Externalizable接口的实例,是最简单的确定对象是否可序列化的做法。然而,这种方法并不能保证我们能成功序列化一个对象。

假设我们有一个不实现Serializable接口的Address对象:

public class Address {
    private int houseNumber;

    //getters and setters
}

当我们尝试序列化Address对象时,可能会出现NotSerializableException

@Test(expected = NotSerializableException.class)
public void whenSerializing_ThenThrowsError() throws IOException {
    Address address = new Address();
    address.setHouseNumber(10);
    FileOutputStream fileOutputStream = new FileOutputStream("yofile.txt");
    try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
        objectOutputStream.writeObject(address);
    }
}

现在,假设我们有一个实现了Serializable接口的Person对象:

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private int age;
    private String name;

    // getters and setters
}

在这种情况下,我们可以序列化并反序列化以重新创建对象:

Person p = new Person();
p.setAge(20);
p.setName("Joe");
FileOutputStream fileOutputStream = new FileOutputStream("yofile.txt");
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
    objectOutputStream.writeObject(p);
}

FileInputStream fileInputStream = new FileInputStream("yofile.txt");
try ( ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
    Person p2 = (Person) objectInputStream.readObject();
    assertEquals(p2.getAge(), p.getAge());
    assertEquals(p2.getName(), p.getName());;
}

3.2. Apache Commons SerializationUtils

另一种验证对象序列化的办法是使用Apache Commons库中的SerializationUtils类的serialize方法。这个方法不会接受不可序列化的对象。

如果我们试图通过显式类型转换来序列化非序列化的Address对象以编译代码,运行时会遇到ClassCastException

Address address = new Address();
address.setHouseNumber(10);
SerializationUtils.serialize((Serializable) address);

让我们用这个方法来验证可序列化的Person对象:

Person p = new Person();
p.setAge(20);
p.setName("Joe");
byte[] serialize = SerializationUtils.serialize(p);
Person p2 = (Person)SerializationUtils.deserialize(serialize);
assertEquals(p2.getAge(), p.getAge());
assertEquals(p2.getName(), p.getName());

3.3. Spring Core SerializationUtils

接下来,我们将看看Spring Core库中的SerializationUtils方法(https://www.javadoc.io/doc/org.springframework/spring-core/5.0.8.RELEASE/org/springframework/util/SerializationUtils.html),它与Apache Commons的方法类似。这个方法也不接受不可序列化的Address对象。

这样的代码在运行时会抛出ClassCastException

Address address = new Address();
address.setHouseNumber(10);
org.springframework.util.SerializationUtils.serialize((Serializable) address);

让我们用可序列化的Person对象试一试:

Person p = new Person();
p.setAge(20);
p.setName("Joe");
byte[] serialize = org.springframework.util.SerializationUtils.serialize(p);
Person p2 = (Person)org.springframework.util.SerializationUtils.deserialize(serialize);
assertEquals(p2.getAge(), p.getAge());
assertEquals(p2.getName(), p.getName());

3.4. 自定义序列化工具

作为第三个选项,我们将创建自己的自定义工具,根据需要进行序列化或反序列化。为了演示这一点,我们将编写两个独立的序列化和反序列化方法。

首先,这是序列化过程中的对象验证示例:

public static  byte[] serialize(T obj) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(obj);
    oos.close();
    return baos.toByteArray();
}

我们还会编写一个执行反序列化过程的方法:

public static  T deserialize(byte[] b, Class cl) throws IOException, ClassNotFoundException {
    ByteArrayInputStream bais = new ByteArrayInputStream(b);
    ObjectInputStream ois = new ObjectInputStream(bais);
    Object o = ois.readObject();
    return cl.cast(o);
}

此外,我们还可以创建一个工具方法,它接受一个Class参数,如果对象可序列化则返回true。这个方法会假设基本类型和接口默认可序列化,并验证输入类是否可以赋值给Serializable,同时在验证过程中排除transientstatic字段。

让我们实现这个方法:

public static boolean isSerializable(Class<?> it) {
    boolean serializable = it.isPrimitive() || it.isInterface() || Serializable.class.isAssignableFrom(it);
    if (!serializable) {
        return false;
    }
    Field[] declaredFields = it.getDeclaredFields();
    for (Field field : declaredFields) {
        if (Modifier.isVolatile(field.getModifiers()) || Modifier.isTransient(field.getModifiers()) || 
          Modifier.isStatic(field.getModifiers())) {
            continue;
        }
        Class<?> fieldType = field.getType();
        if (!isSerializable(fieldType)) {
            return false;
        }
    }
    return true;
}

现在,让我们验证我们的工具方法:

assertFalse(MySerializationUtils.isSerializable(Address.class));
assertTrue(MySerializationUtils.isSerializable(Person.class));
assertTrue(MySerializationUtils.isSerializable(Integer.class));

4. 总结

在这篇文章中,我们探讨了多种判断对象是否可序列化的途径。我们也展示了如何实现一个自定义方法来完成同样的任务。

如往常一样,本教程的所有代码示例都可以在GitHub上找到:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-serialization