今天在解决一个对象的持久化问题时,需要用到序列化技术。一开始,我想用 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();
}
}
问题虽然解决了,但是同样是 Serializable 接口,为什么一个需要默认构造函数,一个不需要?
java原生序列化中反序列化时,是如何创建对象的 这篇文档中有贴源码,我这里就不贴源码了:
// cl 在这里就是我们的 User.class,这段代码在 ObjectStreamClass 的静态方法 getExternalizableConstructor 中
Constructor<?> cons = cl.getDeclaredConstructor((Class<?>[]) null);
// 这段代码是从 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>
下面这个类来自于 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);
}
}
}
原文:https://www.cnblogs.com/kendoziyu/p/how-create-java-bean-when-java-serialization.html