Loading

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查看字节流的数据:

image-20210810165322440

这里有两个关键的标识

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下个断点

image-20210811095923265

进入ObjectInputStream#readObject方法,看到其调用了readObject0

image-20210811100057216

跟进readObject0,在此函数中,根据了tc值来进行switch,此时的tc值为TC_OBJECT,也就是0x73十进制数115

image-20210811100156965

case TC_OBJECT中,调用了readOrdinaryObject

image-20210811100423994

跟进readOrdinaryObject中,发现调用了readClassDesc方法,并把值赋给了desc

image-20210811100756064

跟进readClassDesc,此方法用来分发处理字节流中TC_CLASSDESC的方法,用switch来选择需要处理的方法

image-20210810170654066

tc的值就是TC_CLASSDESC的值0x72,转成10进制就是114,然后进入switch判断后转到case TC_CLASSDESC:

image-20210810172130719

跟进readNonProxyDesc方法,调用了resolveClass方法后,把值赋给了cl

image-20210811101401573

继续跟进resolveClass后,发现使用了Class.forName来创建了ObjectA对象,然后把创建的对象进行return返回

image-20210811101525957

回到readNonProxyDesc,此时cl值变成了ObjectA对象,然后把cl对象传入了initNonProxy,并且赋值给了desc

image-20210811101942110

最后readNonProxyDesc方法返回了desc的值

image-20210811102036139

返回的desc的值赋给了调用处readClassDesc的descriptor,然后又进行了返回

image-20210811102400430

最后回到了readOrdinaryObject方法中

image-20210811102444320

在这里先总结下这几个方法分别做了什么操作

总结下上面的流程顺序为:

readOrdinaryObject -》 readClassDesc -》 readNonProxyDesc -》 readResolve方法

readClassDesc :分发用于处理字节流中TC_CLASSDESC的方法,用switch来选择需要处理的方法

image-20210810170654066

readNonProxyDesc :真正用来处理字节流中的TC_CLASSDESC方法,会调用resolveClass进行创建反序列化的对象

resolveClass:是用Class.forName来创建ObjectA对象的地方,在这里可以做一个检查校验,用于反序列化拦截。weblogic中补丁拦截就是在此进行的

到这里三个方法都讲了用途后,还剩最后一个readOrdinaryObject 了

readOrdinaryObject

这里的readOrdinaryObject就是真正操作调用序列化类中,readObject、readResolve、readExternal方法的地方

接着上面debug,拿到了desc的值后往下走,做了一个判断desc.isExternalizable,如果序列化的接口是Externalizable类型,就进入readExternalData,否则进入readSerialData

image-20210811104539621

此处的ObjectA对象接口类型是Serializable,所以进入了readSerialData方法

image-20210811104910320

最后readSerialData方法中用了反射进行调用反序列化对象的readObject方法

image-20210811104948111

回到readOrdinaryObject,接下来就是调用readResolve方法的地方了

image-20210811105200717

用if进行判断,为true则用反射调用反序列化对象的readResolve方法

image-20210811105328464

image-20210811105436393

引用一张xz上师傅文章的流程图:

https://xz.aliyun.com/t/8443#toc-2

20201027155537-cca7961c-1829-1

总结

方法调用的流程顺序:

readOrdinaryObject -》 readClassDesc -》 readNonProxyDesc -》 readResolve方法

readClassDesc :分发用于处理字节流中TC_CLASSDESC的方法,用switch来选择需要处理的方法

image-20210810170654066

readNonProxyDesc :真正用来处理字节流中的TC_CLASSDESC方法,会调用resolveClass进行创建反序列化的对象

resolveClass:是用Class.forName来创建ObjectA对象的地方,在这里可以做一个检查校验,用于反序列化拦截。weblogic中补丁拦截就是在此进行的

readOrdinaryObject:用于调用序列化类中,readObject、readResolve、readExternal的方法

posted @ 2021-08-11 11:17  yyhuni's  阅读(135)  评论(2编辑  收藏  举报