之前介绍过java内置的序列化,今天总结下java的反序列化,主要介绍一点吧,它是怎么跳过构造函数生成对象的,当然跳过不是故意的,因为它本身实现就没依赖构造函数。比如我们定义了一个private的构造函数的bean,它序列化后的数据仍然可以反序列化出来。总之吧,反序列化不会触发构造函数的逻辑。
反序列化时调用链:
objectInputStream.readObject()-> objectInputStream.readObject0()->objectInputStream.readOrdinaryObject()->ObjectStreamClass.newInstance()->Constructor.newInstance(),随着调用链,我们将会看到如下的调试信息:
然后就返回一个带类型的对象了,看不到任何调试信息,甚至不知道ca是哪个类。所以我们只能使用debug版本的jdk了。基于openjdk源码编译jdk
通过debug的jdk我们获取到ca的类型为GeneratedSerializationConstructorAccessor1,这个类是jvm运行时生成的,要看里面的信息只能dump class文件了,如何dump class文件,里面的实现很优雅,贴个简单可行的。
sudo java -classpath "$JAVA_HOME/lib/sa-jdi.jar" -Dsun.jvm.hotspot.tools.jcore.filter=sun.jvm.hotspot.tools.jcore.PackageNameFilter -Dsun.jvm.hotspot.tools.jcore.PackageNameFilter.pkgList=sun.reflect sun.jvm.hotspot.tools.jcore.ClassDump <pid>
如果报错再试试product版本的jdk,能dump 出字节码文件就行。在当前目录就会生成一个sun包,进去就可以找到GeneratedSerializationConstructorAccessor1了。
这个字节码文件是不能反编译为java文件的,因为它违法语言规范,但是满足字节码文件格式。里面大致的逻辑就是使用new 字节码生成一个空对象,然后调用Object的构造方法进行初始化。
生成对象之后就是相关数据的赋值了,最后我们需要的对象。正常我们new一个对象或者通过反射构造一个对象,最终都会调用 new,invokespecial字节码指令,对于我们来说他们是一体的。new指令会在内存中开辟一块空间,invokespecial会调用构造函数,他们针对的类型都是一样的。而这里new开辟的是我们定义的类的类型,而invokespecial调用的是Object的构造函数。
参考:
不用构造方法也能够生成对象