JAVA反序列化的简单探究
JAVA反序列化的简单探究
本文主要是探究,在反序列化过程中是怎么调用到readObject、readResolve、readExternal方法的问题
新建一个需要被序列化的类ObjectA,写入readResolve和readObject方法:
package com.yy.serialize.readResolve;
import java.io.IOException;
import java.io.Serializable;
public class ObjectA implements Serializable {
private ObjectA() {
}
private static final ObjectA objectA = new ObjectA();
public static ObjectA getInstance() {
return objectA;
}
private Object readResolve() {
System.out.println("执行了readResolve方法");
return objectA;
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
//执行默认的readObject()方法
in.defaultReadObject();
System.out.println("执行了readObject方法");
}
}
另一个类ObjectB则写入了readExternal方法:
package com.yy.serialize.readResolve;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class ObjectB implements Externalizable {
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("执行了writeExternal");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("执行了readExternal");
}
}
测试类:
package com.yy.serialize.readResolve;
import java.io.*;
public class SerializeDemo {
public static void main(String[] args) throws Exception {
ObjectA objectA = ObjectA.getInstance();
ObjectB objectB = new ObjectB();
// 序列化
FileOutputStream fos = new FileOutputStream("a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(objectA);
oos.flush();
// 反序列化
FileInputStream fis = new FileInputStream("a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
}
}
运行测试类生成a.txt文件后,可以使用SerializationDumper查看字节流的数据:
这里有两个关键的标识
TC_OBJECT:标记后面的数据为Object对象
TC_CLASSDESC:类描述符标识,表示一个类中的所有信息
TC_CLASSDESC - 0x72
className
Length - 36 - 0x00 24
Value - com.yy.serialize.readResolve.ObjectA - 0x636f6d2e79792e73657269616c697a652e726561645265736f6c76652e4f626a65637441
serialVersionUID - 0xee 44 c6 e6 0d b3 64 b5
newHandle 0x00 7e 00 00
classDescFlags - 0x02 - SC_SERIALIZABLE
fieldCount - 0 - 0x00 00
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
调用分析
在ois.readObject
下个断点
进入ObjectInputStream#readObject
方法,看到其调用了readObject0
跟进readObject0
,在此函数中,根据了tc值来进行switch,此时的tc值为TC_OBJECT,也就是0x73十进制数115
在case TC_OBJECT
中,调用了readOrdinaryObject
跟进readOrdinaryObject
中,发现调用了readClassDesc
方法,并把值赋给了desc
跟进readClassDesc
,此方法用来分发处理字节流中TC_CLASSDESC的方法,用switch来选择需要处理的方法
tc的值就是TC_CLASSDESC的值0x72,转成10进制就是114,然后进入switch判断后转到case TC_CLASSDESC:
跟进readNonProxyDesc
方法,调用了resolveClass
方法后,把值赋给了cl
继续跟进resolveClass
后,发现使用了Class.forName
来创建了ObjectA对象,然后把创建的对象进行return返回
回到readNonProxyDesc
,此时cl值变成了ObjectA对象,然后把cl对象传入了initNonProxy
,并且赋值给了desc
最后readNonProxyDesc
方法返回了desc的值
返回的desc的值赋给了调用处readClassDesc
的descriptor,然后又进行了返回
最后回到了readOrdinaryObject
方法中
在这里先总结下这几个方法分别做了什么操作
总结下上面的流程顺序为:
readOrdinaryObject -》 readClassDesc -》 readNonProxyDesc -》 readResolve方法
readClassDesc :分发用于处理字节流中TC_CLASSDESC的方法,用switch来选择需要处理的方法
readNonProxyDesc :真正用来处理字节流中的TC_CLASSDESC方法,会调用resolveClass进行创建反序列化的对象
resolveClass:是用Class.forName
来创建ObjectA对象的地方,在这里可以做一个检查校验,用于反序列化拦截。weblogic中补丁拦截就是在此进行的
到这里三个方法都讲了用途后,还剩最后一个readOrdinaryObject 了
readOrdinaryObject
这里的readOrdinaryObject
就是真正操作调用序列化类中,readObject、readResolve、readExternal方法的地方
接着上面debug,拿到了desc的值后往下走,做了一个判断desc.isExternalizable
,如果序列化的接口是Externalizable类型,就进入readExternalData,否则进入readSerialData
此处的ObjectA对象接口类型是Serializable,所以进入了readSerialData
方法
最后readSerialData
方法中用了反射进行调用反序列化对象的readObject
方法
回到readOrdinaryObject
,接下来就是调用readResolve
方法的地方了
用if进行判断,为true则用反射调用反序列化对象的readResolve
方法
引用一张xz上师傅文章的流程图:
https://xz.aliyun.com/t/8443#toc-2
总结
方法调用的流程顺序:
readOrdinaryObject -》 readClassDesc -》 readNonProxyDesc -》 readResolve方法
readClassDesc :分发用于处理字节流中TC_CLASSDESC的方法,用switch来选择需要处理的方法
readNonProxyDesc :真正用来处理字节流中的TC_CLASSDESC方法,会调用resolveClass进行创建反序列化的对象
resolveClass:是用Class.forName
来创建ObjectA对象的地方,在这里可以做一个检查校验,用于反序列化拦截。weblogic中补丁拦截就是在此进行的
readOrdinaryObject:用于调用序列化类中,readObject、readResolve、readExternal的方法