一、序列化与反序列化疑惑
我们都知道序列化的特点:
- 如果某个类能够被序列化,其子类也可以被序列化。
- 声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态, transient代表对象的临时数据。
我们也知道平时写类涉及到交互的时候,基本上都需要实现Serializable接口。
如下图所示:
可是为什么要这么写,我们好像并未太过于关注这点。当我们开始疑惑这点的时候,我们点到Serializable 接口里面的时候,却发现是下图这样的。
不知道你是不是开始时候也和我一样疑惑呢?空的接口,为什么空的就能实现序列化呢?
莫慌莫慌,我们在这个接口的注释上面可以找到对应的答案。
注释很长:具体说的是什么,我这里先跳过,感兴趣的小伙伴们可以自行读一下,读不懂的也可以直接翻译。上图中截图有介绍序列化id的作用。大家也可以读下。下文中会介绍这个接口的作用。
先看一个序列化demo吧,我们就序列化上图中的Studen类
先编写一个测试方法。
@Test
public void testObjectOutputSerializable() throws Exception {
Student liLei = new Student(1, "李雷", 20);
FileOutputStream liLeiOutputStream = new FileOutputStream("D:\\serializable\\liLei.txt");
ObjectOutputStream liLeiOs = new ObjectOutputStream(liLeiOutputStream);
//写入本地磁盘
liLeiOs.writeObject(liLei);
//关闭流
liLeiOs.close();
liLeiOutputStream.close();
}
这里运行一下,查看本地磁盘,已经把文件写入磁盘了。
我们再编写另一个测试方法,把李雷创建回来如下:
@Test
public void testObjectInputSerializable() throws Exception {
FileInputStream liLieInputStream = new FileInputStream("D:\\serializable\\liLei.txt");
ObjectInputStream in = new ObjectInputStream(liLieInputStream);
Student liLei = (Student) in.readObject();
in.close();
liLieInputStream.close();
System.out.println(liLei);
}
这时候我们看到控制台已经打印出我刚才创建的李雷了。
这里没有任何问题。这里稍微提一下,有时候大家面试会碰见java中创建对象的几种方式。
- new 一个对象,可能会考察jvm内存开辟
- 反射,考察反射基本知识。
- 反序列化, 就是本文中要讲到的注意事项。
- clone ,这个一般考察深克隆和浅克隆之前的关系与实现
现在继续回来分析序列化和反序列化。
这时候,我们把Student 类中的 implements Serializable 去掉。执行第一个测试方法。
我们发现报错了。还记得上文中我们看过源码,Serializable接口中是空方法。为什么加上就能序列化到本地磁盘,不加就不能序列化到本地磁盘呢?
再看下一个例子。
我们先把Student类 implements Serializable 然后再添加一个序列化id
如下图:
我们先执行序列化测试方法,再把Student 中的序列化id去掉。然后执行反序列化方法。
反序列化失败。 同样只要修改了序列化id后都会报这个错误。
序列化源码查看
通过测试方法中我们可以看出序列化用的是 ObjectOutputStream 类writeObject()方法序列化到本地中去的。我们就追一下。
第一步
我们通过debug 可以发现序列化进入了 writeObject0() 方法中了。
第二步
我们进去 writeObject0() 方法,会发现一堆验证,我们一步一步的往下走。
在序列化中
java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>) 方法 其中一个方法是获取类的中序列化,如果没有,这里就生成一个默认的。
在java.io.ObjectStreamClass#getDefaultSerialFields中提现了转换字段。
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
//当字段static 或者 TRANSIENT 的时候对应字段不序列化,这就是为什么用transient 修饰字段为什么不序列化的原因、
int mask = Modifier.STATIC | Modifier.TRANSIENT;
for (int i = 0; i < clFields.length; i++) {
if ((clFields[i].getModifiers() & mask) == 0) {
list.add(new ObjectStreamField(clFields[i], false, true));
}
}
int size = list.size();
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}
跳出这个方法回到 writeObject0 方法突然发现一个比较重要的东西。如下图:
是不是突然恍然大悟。为什么 Serializable 中的方法为空,原来只是起个标识作用。 至此我你们的疑问已经解开。我们就继续把代码追完吧。
第三步,
我们进入writeOrdinaryObject 方法,接着执行 writeSerialData() 方法,在writeSerialData()方法中调用defaultWriteFields方法 如下图:
里面又进行数据处理。然后最后执行
bout.write 把数据写入磁盘
这样一个对象就被序列化到磁盘中去了。
注意:网上好多博客没有追源码,序列化顺序把 slotDesc.invokeWriteObject(obj, this); 这个方法也加入到了类的序列化中其实是错误的。类序列化不会走这个方法。走的defaultWriteFields(obj, slotDesc)这个方法,集合类型才会走这个方法。
反序列化源码查看
同样在反序列化中核心方法在java.io.ObjectInputStream#readObject()方法中。我们开始调试一下。
分析
同序列化方法入口重要的方法也是readObject0() 方法。
通过调试,我们进了readOrdinaryObject方法
在readOrdinaryObject 方法中,调用入下图所示:
在readOrdinaryObject 中
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
//这个方法中 switch 语句块匹配 TC_CLASSDESC 调用readNonProxyDesc
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
........
}
在java.io.ObjectInputStream#readNonProxyDesc 中判断序列化id。
接着回到 readOrdinaryObject 方法中java.io.ObjectInputStream#readSerialData方法执行数据填充。
小结
这里只是简单介绍序列化和反序列化的流程。源码上的逻辑还是挺多,这里需要大家慢慢的去品读。
在结束的时候,我来把上次反序列化方式破坏单例,再处理一下。
通过反序列化源码我们可以知道:序列化会通过反射调用无参数的构造方法创建一个新的对象。
这时候就破坏了单例。
我们再源码中找到了如下方法:
如果以上的的判断为false 就不会执行下面的代码,就会通过反射生成新的对象。
Object invokeReadResolve(Object obj)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (readResolveMethod != null) {
try {
//这里通过反射生成对象。
return readResolveMethod.invoke(obj, (Object[]) null);
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof ObjectStreamException) {
throw (ObjectStreamException) th;
} else {
throwMiscException(th);
throw new InternalError(th); // never reached
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
反射的方法可以调试一下,在这里赋值的。同时对象里面需要加上public Object readResolve() 方法
可知只需要在单例模式上添加一段代码即可,这里就不做详细的测试了,大家可以自行测试。
public class DoubleCheckSingleton {
public DoubleCheckSingleton() {
}
public class DoubleCheckVolatileSingleton {
public DoubleCheckVolatileSingleton() {
}
public static volatile DoubleCheckVolatileSingleton singleton = null;
public static DoubleCheckVolatileSingleton getInstance() {
if (null == singleton) {
synchronized (DoubleCheckVolatileSingleton.class) {
if (null == singleton) {
singleton = new DoubleCheckVolatileSingleton();
}
}
}
return singleton;
}
//防止反序列化破坏单例
public Object readResolve() {
return singleton;
}
}