Java反序列化时是否通过默认构造函数创建对象?

问题背景

今天在解决一个对象的持久化问题时,需要用到序列化技术。一开始,我想用 fastjson,但是麻烦的是这个框架是基于 getter 方法来序列化对象的,可是我序列化的对象不是一个标准的 Java Bean 对象,没有 getter/setter 方法。而我的需求是根据字段(类成员变量)来序列化对象。然后我就想到了使用 Java 序列化技术,并且配合使用 transient 屏蔽不需要参与序列化的字段(属性)。更多 transient 关键字的信息可以参考这篇文章:transient 变量修饰符
但是问题来了,先上代码:

// User 类是我要序列化保存,并且再用反序列化恢复的类
public class User implements Serializable {

    private String userName;

    private transient List<String> tags;

    public User(String userName) {
        this.userName = userName;
        this.tags = new ArrayList<String>();
    }

    public User(String userName, List<String> tags) {
        this.userName = userName;
        this.tags = tags;
    }

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

我的测试类是

public class JavaSerializationTest {

    @Test
    public void test1() {
        User user = new User("kendoziyu", Arrays.asList("cool", "smart"));
        byte[] data = SerializationUtils.serialize(user);

        User copyUser = SerializationUtils.deserialize(data);
        System.out.println(copyUser);

        User expiredUser = new User("kendoziyu");
        System.out.println(expiredUser);
    }
}

结果:

问题所在:
我希望反序列化之后 tags 对象是一个空集合,但不是一个 null!

后来我也找到了替代方案:
Externalizable 是 Serializable 的子类接口,Externalizable 是一个手动序列化接口,而 Serializable 是一个自动序列化接口。

public class NewUser implements Externalizable {

    private String userName;

    private List<String> tags;

    public NewUser() {
        tags = new ArrayList<String>();
    }

    public NewUser(String userName) {
        this.userName = userName;
        this.tags = new ArrayList<String>();
    }

    public NewUser(String userName, List<String> tags) {
        this.userName = userName;
        this.tags = tags;
    }

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

    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.userName);
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.userName = (String) in.readObject();
    }
}
  • 此时不需要加入 transient 关键字了,取而代之的是,在 writeExternal 方法中,手动编码控制需要序列化哪些字段。
  • 这个方法需要一个默认的构造函数,否则会抛出异常 org.apache.commons.lang3.SerializationException: java.io.InvalidClassException: test.java.serialization.NewUser; no valid constructor
  • 当然有了默认构造函数之后,我们就可以在默认构造函数中,初始化我们的集合

产生疑惑

问题虽然解决了,但是同样是 Serializable 接口,为什么一个需要默认构造函数,一个不需要?

分析源码

java原生序列化中反序列化时,是如何创建对象的 这篇文档中有贴源码,我这里就不贴源码了:

  • 假如,反序列化的目标类实现了 Externalizable 接口,那么就通过反射得到就是该目标类对应的默认构造函数:
// cl 在这里就是我们的 User.class,这段代码在 ObjectStreamClass 的静态方法 getExternalizableConstructor 中
Constructor<?> cons = cl.getDeclaredConstructor((Class<?>[]) null);
  • 假如,反序列化的目标类实现了 Serializable 接口,那么就会获取 Object 的默认构造函数
// 这段代码是从 ObjectStreamClass 的静态方法 getSerializableConstructor 中节选的,它会一直调用 getSuperClass 找父类
// 最终找到了 Object,并且获取了 Object 的默认构造函数
Class<?> initCl = cl;
while (Serializable.class.isAssignableFrom(initCl)) {
    Class<?> prev = initCl;
    if ((initCl = initCl.getSuperclass()) == null ||
        (!disableSerialConstructorChecks && !superHasAccessibleConstructor(prev))) {
        return null;
    }
}
Constructor<?> cons = initCl.getDeclaredConstructor((Class<?>[]) null);
...

获取到了构造函数对象 Constructor,就可以通过反射创建 Java 对象了:

// 实例化的调用
cons.newInstance();

但是有一个比较纳闷的问题,那就是用 Object 的构造函数为啥还能创建子类对象??
我们可以看一下 Constructor#newInstance 方法:

public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    ...
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

实例化,还是要靠 ConstructorAccessor 实例。而此时该对象实例,是在 MethodAccessorGenerator#generateSerializationConstructor 方法中通过 asm 操作字节码返回的全新SerializationConstructorAccessorImpl 对象,通过这个对象就可以创建我们想要的 User 对象了。但是这种方式反序列化创建对象时,是不会调用我们源代码 User 中的默认构造函数的。

附录

引入工具包

序列化的方法,在 Apache 中已经有了实现,参考 org.apache.commons.lang3.SerializationUtils 。你们可以使用以下 maven 配置引入依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.11</version>
</dependency>

序列化工具类 SerializationUtils

下面这个类来自于 org.apache.commons.lang.SerializationUtils,是序列化的工具类。如果不想导入包,可以直接用这段代码:

public class SerializationUtils {
    public SerializationUtils() {
    }

    public static Object clone(Serializable object) {
        return deserialize(serialize(object));
    }

    public static void serialize(Serializable obj, OutputStream outputStream) {
        if (outputStream == null) {
            throw new IllegalArgumentException("The OutputStream must not be null");
        } else {
            ObjectOutputStream out = null;

            try {
                out = new ObjectOutputStream(outputStream);
                out.writeObject(obj);
            } catch (IOException var11) {
                throw new SerializationException(var11);
            } finally {
                try {
                    if (out != null) {
                        out.close();
                    }
                } catch (IOException var10) {
                }

            }

        }
    }

    public static byte[] serialize(Serializable obj) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
        serialize(obj, baos);
        return baos.toByteArray();
    }

    public static Object deserialize(InputStream inputStream) {
        if (inputStream == null) {
            throw new IllegalArgumentException("The InputStream must not be null");
        } else {
            ObjectInputStream in = null;

            Object var2;
            try {
                in = new ObjectInputStream(inputStream);
                var2 = in.readObject();
            } catch (ClassNotFoundException var12) {
                throw new SerializationException(var12);
            } catch (IOException var13) {
                throw new SerializationException(var13);
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException var11) {
                }

            }

            return var2;
        }
    }

    public static Object deserialize(byte[] objectData) {
        if (objectData == null) {
            throw new IllegalArgumentException("The byte[] must not be null");
        } else {
            ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
            return deserialize((InputStream)bais);
        }
    }
}
posted @ 2020-09-28 21:08  极客子羽  阅读(2194)  评论(1编辑  收藏  举报