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);
}
}
}