Java 对象序列化踩坑经验

0 背景

有一个集群相关的 bug,在我名下挂了两三个月,今天终于解决掉了,居然跟对象序列化有关。问题简化之后的现象是:一个 Java 对象,存入 redis 后再取出来,丢失了 id 字段

1 结论

对于需要序列化的类,一定要在继承层次的最顶层实现 Serializable 接口,否则父类的私有字段无法序列化。

2 实验研究

2.1 准备工作

我们创建两个类,一个是 Animal,另一个是 Dog。

源码如下:

// Animal.java
abstract class Animal {
    private String id;

    Animal() {
    }

    Animal(String id) {
        this.id = id;
    }

    String getID() {
        return id;
    }

    void setID(String id) {
        this.id = id;
    }
}

// Dog.java
class Dog extends Animal implements Serializable {
    private String name;
    Dog(String id, String name) {
        super(id);
        this.name = name;
    }

    String getName() {
        return name;
    }

    void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Dog)) {
            return false;
        }
        return getID().equals(((Dog)obj).getID())
                && getName().equals(((Dog)obj).getName());
    }

    @Override
    public String toString() {
        return "Dog[" + getID() + "]: " + getName();
    }
}

2.2 复现问题

编写如下单元测试,运行,发现有报错。基本还原了我遇到的 bug。

public class DogTest {
    private Dog dog;

    @Before
    public void setUp() {
        dog = new Dog("dog-001", "WangCai");
    }

    @Test
    public void testSerializable() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream os = new ObjectOutputStream(baos);
        os.writeObject(dog);
        os.close();

        ObjectInputStream is = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
        Dog dog2 = (Dog)is.readObject();
        is.close();

        assertEquals(dog, dog2);  // 报错
    }
}

截图为证

2.3 修复问题

让 Animal 去实现 Serializable 接口(而不是让 Dog 去实现)。

修改后的代码如下(省略号代表未改动的代码):

// Animal.java
abstract class Animal implements Serializable {
...

// Dog.java
class Dog extends Animal {
...

再跑一遍单元测试,发现没有问题了。