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的方法

最新文章

  1. 特大喜讯,View and Data API 现在支持中文界面了
  2. C++类成员布局
  3. Objective-C之集合对象的内存管理
  4. Python文件操作之简化代码
  5. Effective Java 07 Avoid finallizers
  6. SDAutolayout图片大小根据数量变化
  7. makefile详解 嵌套执行make,定义命令包
  8. jquery 日期控件
  9. Moderate 加入空格使得可辨别单词数量最多 @CareerCup
  10. TypeScript-01-变量、基本类型和运算符
  11. Spring Boot快速入门(一): Hello Spring Boot
  12. [Swift]LeetCode203. 移除链表元素 | Remove Linked List Elements
  13. 【安卓基础】ImageView与EditText联动实现隐藏与显示密码
  14. learning makefile 模式规则
  15. scrapy_redis项目配置
  16. 类 Arrays StringBuilder 跟 StringBuffer 的异同 SimpleDateFormat
  17. SQLite之C#连接SQLite
  18. vue属性值调方法
  19. vue开发 - 将方法绑定到window对象,给app端调用
  20. php文件每隔几秒执行一次

热门文章

  1. 内置函数 字符串比较 strcmp 登录密码
  2. Android太太太太太卷了,累了
  3. 我,Android开发5年,32岁失业,现实给我狠狠上了一课!
  4. IntelliJ IDEA常用的快捷键积累总结
  5. vue3.0安装
  6. C语言中的stdin,stdout,stderr[转]
  7. MySQL为什么不支持中文排序?
  8. 在docker安装tomcat的时候,报错:Caused by: java.lang.IllegalArgumentException: The AJP Connector is configured with secretRequired="true
  9. 数学log的基本知识
  10. C# 获得文件的执行路径的方法