> 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); }