前言: 为了Java反序列化漏洞打下深厚基础,特意学习总结了Java序列化机制及源码解读系列,便于分享和日后巩固。
主要记录以下四个方面:
1、Serializable和Externalizable,序列化接口
2、ObjectOutputStream对象输出流深入解读
3、ObjectInputStream对象输入流深入解读
4、拓展知识学习
初识序列化 首先认识下序列化。序列化就是把对象转换成字节流 ,便于保存在内存、文件、数据库中;反序列化即逆过程,把字节流还原成对象。Java序列化时会序列化该对象的类信息、属性及属性值,不能序列化对象的方法。
先看下序列化用途:
(1)永久性保存对象,保存对象的字节序列到本地文件或者数据库中 ;
(2)通过序列化以字节流的形式使对象在网络中进行传递和接收 ;
(3)通过序列化在进程间传递对象 。
一些知识铺垫:
1、标记接口:Java中的标记接口(Marker Interface),又称标签接口(Tag Interface),具体是不包含任何方法的接口。在Java中,标记接口主要有以下两种目的:一是建立一个公共的父接口。比如EventListener接口,一个由几十个其它接口扩展的Java API,当一个接口继承了EventListener接口,JVM就知道该接口将要被用于一个事件的代理方案。同样的,你可以使用一个标记接口来建立一组接口的父接口。二是向一个类添加数据类型。这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过Java的多态性可以变成一个接口类型。
标记接口因为没有任何方法所以其本身并不“工作”,顾名思义,它只是将类标记为特定类型。在一些代码中可以检查标记是否存在,并根据这些信息进行一些操作,例如可以通过if (instance instanceof MyMarkerInterface) {...}
来进行判断某个对象是否是标记类的实例化,然后再去进行一些操作。
如何序列化和反序列化? 一般序列化至少需要以下步骤:
创建一个类实现Serializable接口,并设置serialVersionUID(最好设置下,默认可不设置。设置方法可以百度IDEA设置serialVersionUID)
创建一个对象输出流ObjectOutputStream
调用对象输出流的writeObject方法把对象转换成字节流输出
一般反序列化至少需要以下步骤:
创建一个对象输入流ObjectInputStream
调用对象输入流的readObject方法把字节流转为对象
实例:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import java.io.*;class CommandExec implements Serializable { private static final long serialVersionUID = -6211228684695072792L ; public void exec () { String cmd = "calc" ; try { Process process = Runtime.getRuntime().exec(cmd); InputStream is = process.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); for (String content = br.readLine(); content != null ; content = br.readLine()) { System.out.println(content); } } catch (IOException var7) { var7.printStackTrace(); } } } public class Main { public static void main (String[] args) throws Exception { CommandExec commandExec1 = new CommandExec(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("e:\\test.txt" ))); oos.writeObject(commandExec1); System.out.println("对象序列化成功!" ); oos.flush(); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("e:\\test.txt" ))); CommandExec commandExec2 = (CommandExec) ois.readObject(); System.out.println("对象反序列化成功!" ); commandExec2.exec(); ois.close(); } }
执行结果:
test.txt文件:
除了java.io.Serializable,JDK还提供了另外一种原生序列化接口java.io.Externalizable,接下来深入学习这两种接口。
Serializable序列化接口 java.io.Serializable是一个标记接口,仅用于标识 类的可序列化,它没有任何接口需要去实现。
这里拓展下:标记接口因为没有任何方法所以其本身并不“工作”,顾名思义,它只是将类标记为特定类型。在一些代码中可以检查标记是否存在,并根据这些信息进行一些操作,例如可以通过if (instance instanceof MyMarkerInterface) {...}
来进行判断某个对象是否是标记类的实例化,然后再去进行一些操作。
重要知识点:
java.io.Serializable仅用于标识 类的可序列化,它没有任何方法或字段
序列化运行时将每个可序列化的类与称为serialVersionUID的版本号相关联, 因为可能这个类可能存在新旧版本, 所以使用该标记来标明。如果收发方的这个类版本不一致, 在反序列化 (deserialization
) 的时候就会抛出InvalidClassException
异常。其修饰符是static final long
。注意写代码时可以用IDEA来自动生成serialVersionUID
只要实现了Serializable,它的所有子类都是可序列化的 ,子类会直接父类的继承writeObject和readObject,注意反序列化时不会调用父类的构造器
父类不可序列化,子类声明该接口,要想序列化父类信息 子类必须重写writeObject和readObject方法,将可访问的父类信息序列化(原因:对象反序列化时,如果父类未实现序列化接口,则反序列出的对象会再次调用父类的构造函数来完成属于父类那部分内容的初始化)
Externalizable序列化接口 Externalizable接口是继承于Serializable接口的. 它仅定义了两个方法分别用于控制序列化和反序列化过程。通过Externalizable我们可以控制哪些对象及属性能够序列化,并且能够控制其按照代码出现顺序 进行序列化。
1 2 void writeExternal (ObjectOutput out) throws IOException ;void readExternal (ObjectInput in) throws IOException, ClassNotFoundException ;。
实例:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 import java.io.*;import java.util.Date;public class TestExternalizable { public static void main (String[] args) throws IOException, ClassNotFoundException { TestExternalizable testExternalizable = new TestExternalizable(); testExternalizable.objToStream(); testExternalizable.streamToObj(); } public void objToStream () throws IOException { MyExternalizable myExternalizable = new MyExternalizable(1 ,2 ,"3" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:\\1.ser" )); objectOutputStream.writeObject(myExternalizable); objectOutputStream.close(); } public void streamToObj () throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("E:\\1.ser" )); MyExternalizable myExternalizable = (MyExternalizable) objectInputStream.readObject(); System.out.println(myExternalizable.toString()); objectInputStream.close(); } } class MyExternalizable implements Externalizable { public int a = 0 ; public int b = 0 ; public String c = null ; public MyExternalizable (int a, int b, String c) { this .a = a; this .b = b; this .c = c; } public MyExternalizable () { } @Override public void writeExternal (ObjectOutput out) throws IOException { System.out.println("序列化..." ); Date d = new Date(); out.writeObject(d); out.writeObject(a); out.writeObject(c); } @Override public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("反序列化..." ); Date d = (Date) in.readObject(); System.out.println(d.getTime()); this .a = (Integer) in.readObject(); this .c = (String) in.readObject(); } @Override public String toString () { return "a:" + a +" b:" + b + " c:" + c; } }
执行结果:
参考链接: https://www.jianshu.com/p/729c15e14d76 https://blog.csdn.net/liwenshui322/article/details/47145191 https://blog.csdn.net/weixin_30485291/article/details/98134295 https://www.cnblogs.com/youxin/archive/2013/06/04/3116304.html