反序列化原理
对象的反序列化主要通过readObject方法实现,其主要流程是从字节流中读取出对象的类型及value信息。
对于普通的对象,通过readOrdinaryObject进行读取。
对于数组,通过readArray进行读取。
对于string类型,通过readString进行读取。
对于enum类型,通过readEnum进行反序列化。
readOrdinaryObject主要分为两个部分:readClassDesc和readSerialData。
readClassDesc用来读取序列化对象的descriptor,包含对象的field name和signature。
readSerialData读取继承Serialize接口的field value,包括primitive类型和obj类型。在对obj value进行反序列化时,会重新调用回readObject方法。
readExternalData是用来读取继承Externalize接口的序列化对象的field value
readOrdinaryObject在读取完对象的descriptor后,紧接着会执行passHandle = handles.assign(obj)方法,将obj存入handles中的objs[]数组,在读取完对象的data后,会执行handles.finish(passHandle)。下次再读取到相同对象时,会直接从handles(readHandle)中读取。
readClassDesc:
reacClassDesc用来读取对象的descriptor信息。根据对象的不同,可能走以下几个路径:
readNull: 对象为null走此路径。
readNonProxyDesc:对象为一个普通非动态代理类对象。
我们的优化主要在这里进行实现:
readHandle:当对象已经被反序列化过一次,则直接从handle中通过lookupObject方法进行查询。
readProxyDesc:动态代理类的对象走此路径
以下是openJDK中的读取descriptor信息的方法
protected ObjectStreamClass readClassDescriptor()
throws IOException, ClassNotFoundException
{
ObjectStreamClass desc = new ObjectStreamClass();
desc.readNonProxy(this);
return desc;
}
在每一次读取时都会创建一个ObjectStreamClass对象,通过调用readNonProxy来获取name/suid/isProxy等信息
name = in.readUTF();
suid = Long.valueOf(in.readLong());
isProxy = false;
最后通过readTypeString()读取出signature
for (int i = 0; i < numFields; i++) {
char tcode = (char) in.readByte();
String fname = in.readUTF();
String signature = ((tcode == 'L') || (tcode == '[')) ?
in.readTypeString() : new String(new char[] { tcode });
try {
fields[i] = new ObjectStreamField(fname, signature, false);
} catch (RuntimeException e) {
throw (IOException) new InvalidClassException(name,
"invalid descriptor for field " + fname).initCause(e);
}
}
然后创建ObjectStreamField
try {
fields[i] = new ObjectStreamField(fname, signature, false);
} catch (RuntimeException e) {
throw (IOException) new InvalidClassException(name,
"invalid descriptor for field " + fname).initCause(e);
}
优化后
毕昇JDK readClassDescriptor()优化后代码如下:
并没有直接创建ObjectStreamClass而是先通过userFastSerializer判断是否存在同一个class descriptor如果存在则直接从缓存中进行读取
protected ObjectStreamClass readClassDescriptor()
throws IOException, ClassNotFoundException
{
// fastSerializer
if (useFastSerializer) {
String name = readUTF();
Class<?> cl = null;
ObjectStreamClass desc = new ObjectStreamClass(name);
try {
// In order to match this method, we add an annotateClass method in
// writeClassDescriptor.
cl = resolveClass(desc);
} catch (ClassNotFoundException ex) {
// resolveClass is just used to obtain Class which required by lookup method
// and it will be called again later, so we don't throw ClassNotFoundException here.
return desc;
}
if (cl != null) {
desc = ObjectStreamClass.lookup(cl, true);
}
return desc;
}
// Default deserialization. If the Class cannot be found, throw ClassNotFoundException.
ObjectStreamClass desc = new ObjectStreamClass();
desc.readNonProxy(this);
return desc;
}
但是这里也会又一些兼容性的问题
在这里的userFastSerializer实际上是由public native boolean getUseFastSerializer();
获取native方法会给我们程序效率带来提高,几乎没有什么毛病,实际不然,使用native方法会带来潜在的安全隐患,因为本机方法执行实际的机器代码,它有权使用主机系统的所有资源,本机执行代码不收java执行环境的限制,因此可能会导致病毒入侵。与此同时,因为使用本机方法,调用dll动态链接库,具体实现代码都是在dll文件中的,因为本机方法都是依赖于CPU和操作系统,因此dll文件在本质上,是不可移植的,从而丧失了程序的可移植性。