> Author: shaobaobaoer
> Codes : https://github.com/ninthDevilHAUNSTER/JavaSecLearning
> Mail: shaobaobaoer@126.com
> WebSite: shaobaobaoer.cn
> Time: Friday, 24. July 2020 09:49PM
Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。把字节序列恢复为对象的过程称为对象的反序列化。
在Java中实现对象反序列化非常简单,实现 java.io.Serializable
(内部序列化)或 java.io.Externalizable
(外部序列化)接口即可被序列化,( Externalizable 实际上是继承了Serializable 的接口 )。
另外 Serializable 是一个空空的接口。仅仅用于标识。
public interface Serializable {
}
对于序列化,有着以下的注意点
sun.reflect.ReflectionFactory.newConstructorForSerialization
对此,我创建了一个Employee类,用于之后的实验
class Employee implements java.io.Serializable {
public String name;
public String address;
public transient int SSN; // 暂时的
public int number;
Employee() {
System.out.println("init Employee Class");
}
public static @NotNull Employee getEmployee() {
return new Employee();
}
public void mailCheck() {
System.out.println("Mailing a check to " + name
+ " " + address);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '/'' +
", address='" + address + '/'' +
", SSN=" + SSN +
", number=" + number +
'}';
}
java.io.ObjectOutputStream
类最核心的方法是 writeObject
方法,即序列化类对象。 java.io.ObjectInputStream
类最核心的功能是 readObject
方法,即反序列化类对象。 其对应操作如下:
// 序列化DeserializationTest类 Employee t = new Employee(...) ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(t); // 反序列化输入流数据为Employee对象 ObjectInputStream in = new ObjectInputStream(bais); Employee e = (Employee) in.readObject();
于是问题来了,Serializables是一个空接口,但是那只能是ObjectOutputStream,ObjectInputStream来操作完成序列化与反序列化了。
在序列化与反序列化的过程中,往往会用到 ObjectInputStream 和 ObjectOutputStream 中的 readObj与writeObj方法。
这两个Stream均继承自java.io.ObjectStreamClass,该方法继承了 Serializable 的接口
在 java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>):532/535行中有着这两行代码
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
虽然我涉世未深,但是也能猜得出,这是在动态获取writeObject这两个方法,并且是Private的
继续跟入,可以发现,总共需要扩展这五个方法
之于具体还是得看源码。(源码探究部分好复杂...本来想着一并写到这个博客里去,但是能力有限。内容很复杂。之后会把这个坑填上,这个博客后续还会补充)
通过上述分析,可以自定义这五个方法,对此也能够看出这五个方法的调用顺序
class Employee{
...
/**
* 自定义反序列化类对象
*
* @param ois 反序列化输入流对象
* @throws IOException IO异常
* @throws ClassNotFoundException 类未找到异常
*/
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
System.out.println("readObject...");
// 调用ObjectInputStream默认反序列化方法
ois.defaultReadObject();
// 省去调用自定义反序列化逻辑...
}
/**
* 自定义序列化类对象
*
* @param oos 序列化输出流对象
* @throws IOException IO异常
*/
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
System.out.println("writeObject...");
// 省去调用自定义序列化逻辑...
}
private void readObjectNoData() {
System.out.println("readObjectNoData...");
}
/**
* 写入时替换对象
*
* @return 替换后的对象
*/
protected Object writeReplace() {
System.out.println("writeReplace....");
return this;
}
protected Object readResolve() {
System.out.println("readResolve....");
return this;
}
}
通过调用图,可以发现,这两个stream均实现了 ObjectStreamConstants
这一个接口,这个接口仅仅包括一些序列化时用到的常量。
PS:通过这个调用图也看不出什么来,具体还是要看函数的调用链...
官方对于这个类的描述如下:
Serialization’s descriptor for classes. It contains the name and serialVersionUID of the class. The ObjectStreamClass for a specific class loaded in this Java VM can be found/created using the lookup method.
ObjectStreamClass这个是类的序列化描述符,这个类可以描述需要被序列化的类的元数据,包括被序列化的类的名字以及序列号。可以通过lookup()方法来查找/创建在这个JVM中加载的特定的ObjectStreamClass对象。
由于之后会用到这个类,对此,需要解析一下其构造方法
private ObjectStreamClass(final Class<?> cl) {
this.cl = cl;
name = cl.getName();
isProxy = Proxy.isProxyClass(cl);
isEnum = Enum.class.isAssignableFrom(cl);
isRecord = isRecord(cl);
serializable = Serializable.class.isAssignableFrom(cl);
externalizable = Externalizable.class.isAssignableFrom(cl);
Class<?> superCl = cl.getSuperclass();
superDesc = (superCl != null) ? lookup(superCl, false) : null;
localDesc = this;
if (serializable) {...}
// 一些错误处理模块
...
}
cl一般是传入 Object.getClass()
, serializable
判断了是否为可序列化对象。直接导致了会不会进到if循环中进行处理。
public Void run() {
...
/*
获取实现Serializable接口对象的字段,实现Externalizable的为空字段
没有声明序列化字段的,则获取默认的序列化字段
*/
// 获取 SUID
suid = getDeclaredSUID(cl); // 下面会解释
try {
fields = getSerialFields(cl); // 下面会解释
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ObjectStreamClass.ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (isRecord) {
canonicalCtr = canonicalRecordCtr(cl);
} else if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
//对于 Serializable接口对象 ,获取writeObject,readObject,readObjectNoData方法
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
// 获取 writeReplace,readResolve方法
domains = getProtectionDomains(cons, cl);
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
serialVersionUID
也是一个非常重要的字段,它是由 getDeclaredSUID 获取的,并且这个字段必须是static final修饰的。 mask = Modifier.STATIC | Modifier.FINAL;
private static Long getDeclaredSUID(Class<?> cl) {
try {
Field f = cl.getDeclaredField("serialVersionUID");
int mask = Modifier.STATIC | Modifier.FINAL;
if ((f.getModifiers() & mask) == mask) {
f.setAccessible(true);
return Long.valueOf(f.getLong(null));
}
} catch (Exception ex) {
}
return null;
}
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out); // new DataOutputStream(this); 绑定一个底层对象
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;
writeStreamHeader(); // 写入文件头
bout.setBlockDataMode(true);// flush数据
if (extendedDebugInfo) { // debug方法
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
其中 writeStreamHeader()将写入一些魔术变量。这两个变量在 ObjectStreamConstants
中有定义
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);
bout.writeShort(STREAM_VERSION);
}
public final void writeObject(Object obj) throws IOException {
...
try {
writeObject0(obj, false);
...
可以发现其调用了一个私有的writeObject0函数,继续追踪
该函数较为复杂,可以分为如下几个部分
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
...
try {
...
Object orig = obj;
// 获取要序列化的对象的Class对象
Class cl = obj.getClass();
ObjectStreamClass desc;
//这是一个循环,目的是判断writeReplace()返回的对象中是否还有这个方法,一直到最后需要序列化的那个对象,并返回这个对象。
//这就是我们可以自定义具体序列化哪个对象。对应了writeReplace()的方法
for (;;) {
Class repCl;
// 创建描述cl的ObjectStreamClass对象
desc = ObjectStreamClass.lookup(cl, true);
//如果有writeReplace()就通过反射执行,方法会返回一个Object对象,如果返回的这个对象的类和本类相同,就不重复执行writeReplace()
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
}
...
// 如果返回的这个对象的类和本类不同,就重复执行writeReplace()方法
...
//如果是字符串、数组、枚举、实现了Serializable接口的对象可以序列化,其余的不可以
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum) obj, desc, unshared);
} else if (obj instanceof Serializable) {
// 被序列化对象实现了Serializable接口
writeOrdinaryObject(obj, desc, unshared);
}
...
}
可见 desc = ObjectStreamClass.lookup(cl, true);
表明了
public ObjectInputStream(InputStream in) throws IOException {
verifySubclass();
bin = new BlockDataInputStream(in);
handles = new HandleTable(10);
vlist = new ValidationList();
serialFilter = ObjectInputFilter.Config.getSerialFilter();
enableOverride = false;
readStreamHeader();
bin.setBlockDataMode(true);
}