在平常的工作过程中,我们经常会提到并且用到序列化和反序列化,RPC的使用就是最好的一个例子,那么到底是序列化和反序列化呢,序列化和反序列化有什么用处,Java又是如何实现序列化和反序列化的呢?
序列化是将对象的状态信息转换为可存储或者传输的形式的过程,简单的来讲就是将对象转换为字节序列的过程。反序列化就是序列化的逆过程,将字节数组反序列化为对象,恢复为对象的过程。那么序列化和反序列化解决哪些问题呢?
将对象序列化为字节数组,使用对象可以通过网络进行进程之间传输并且可以将对象永久存储到存储设备。这就是序列化主要解决的问题—存储问题和传输问题。
下面我们使用Java原生的序列化方式写一个简单的序列化和反序列化的例子,Java的序列化主要通过java.io.ObjectOutputStream和java.io.ObjectInputStream来实现,其中序列化的对象需要实现java.io.Serializable接口,如下,我们定义一个对象并且实现Serializable接口:
public class User implements Serializable {
private static final long serialVersionUID = 2925461581469771001L;
private int id;
private String name;
......//setter和getter方法
}
下面我们编写对象序列化和反序列化的代码,这里我们会用到前面写的ObjectOutputStream和ObjectInputStream类,示例代码如下:
public class JdkSerializerImpl implements ISerializer {
//序列化
@Override
public byte[] serizlize(T obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
return byteArrayOutputStream.toByteArray();
}
//反序列化
@Override
public T unserizlize(byte[] data) throws ClassNotFoundException, IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
}
}
上面我们使用Java原生的序列化方式,编写了一个Java序列化的例子,下面我们使用Socket作为一个例子,介绍对象使用Socket通过网络传输,我们在客户端传输User对象,并且在服务端接收被序列化过的User对象,反序列化打印出User的信息。如下为客户端的代码:
//连接socket
Socket socket = new Socket("127.0.0.1", 9090);
//创建User对象
User user = new User();
user.setId(1);
user.setName("pharos");
//通过Socket传输对象
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
out.writeObject(user);
socket.shutdownOutput();
//接收服务端传输的对象
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
user = (User) objectInputStream.readObject();
//打印传出的传输的对象User [id=1, name=receive]
System.out.println(user.toString());
socket.close();
如上代码为Socket客户端,通过Socket发送User对象到服务端,并且从服务端接收修改过的User对象,下面我们继续查看服务端代码:
//创建服务端
ServerSocket serverSocket = new ServerSocket(9090);
while(true) {
//接收Socket
Socket socket = serverSocket.accept();
//获取Socket传输过来的User对象
InputStream in = socket.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(in);
//通过反序列化获取User对象
User user = (User) objectInputStream.readObject();
//打印接收到的User对象
System.out.println(user.toString());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
//修改User名称
user.setName("receive");
//将User对象传输到客户端
objectOutputStream.writeObject(user);
}
上面的代码是最简单的RPC的调用,也是很多RPC协议的雏形,不过RPC框架的处理更为复杂,其基本核心无外乎是序列化对象的传输与反序列化。下面我们了解一下序列化和反序列化的原理,也就是上面使用的readObject()方法和writeObject(obj)方法。
public final Object readObject()throws IOException, ClassNotFoundException{
.....
try {
//反序列化主要逻辑在readObject0()方法中
Object obj = readObject0(false);
......//其他逻辑
}
......//其他逻辑
}
从上面的代码中我们看到其核心在反序列化主要逻辑在readObject0()方法中,下面我们继续查看readObject0()方法,在该方法中不同类型的对象,调用不同的方式进行反序列化,核心如下代码:
switch (tc) {
//如果为NULL类型
case TC_NULL:
return readNull();
......//其他类型
//枚举类型
case TC_ENUM:
return checkResolve(readEnum(unshared));
//Object类型
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
//其他类型
}
上面代码调用了readOrdinaryObject()方法,其最终调用的方法为
void invokeReadObjectNoData(Object obj)throws IOException, UnsupportedOperationException{
requireInitialized();
if (readObjectNoDataMethod != null) {
try {
readObjectNoDataMethod.invoke(obj, (Object[]) null);
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof ObjectStreamException) {
throw (ObjectStreamException) th;
} else {
throwMiscException(th);
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
通过源码我们可以看到,readObject 最终是通过反射来实现的。其实我们可以在很多地方看到 readObject 和 writeObject 的使用,比如 HashMap。同样writeObject最终调用了invokeWriteObject,也是通过反射实现。
void invokeWriteObject(Object obj, ObjectOutputStream out)throws IOException, UnsupportedOperationException{
requireInitialized();
if (writeObjectMethod != null) {
try {
writeObjectMethod.invoke(obj, new Object[]{ out });
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof IOException) {
throw (IOException) th;
} else {
throwMiscException(th);
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
前面我们已经初步了解了 Java 序列化的知识,除了Java自己提供的序列化方案,目前还有开源了很多其他的序列化框架,比如XML 序列化框架,JSON 序列化框架,Hessian 序列化框架,Avro 序列化,kyro 序列化框架,Protobuf 序列化框架等等。
XML 序列化的好处在于可读性好,同时 XML 又具有语言无关性,所以还可以用于异构系统之间的数据交换和协议。比如我们熟
知的 Webservice,就是采用 XML 格式对数据进行序列化的。XML 序列化/反序列化的实现方式有很多,熟知的方式有 XStream 和 Java 自带的 XML 序列化和反序列化两种,具体的使用方式我们这里不再介绍。
JSON 序列化常用的开源工具有很多Jackson (https://github.com/FasterXML/jackson),阿里开源的 FastJson ,Google 的 GSON (https://github.com/google/gson)这几种 json 序列化工具中,Jackson 与 fastjson 要比 GSON 的性能要好,但是 Jackson、GSON 的稳定性要比 Fastjson 好。而 fastjson 的优势在于提供的 api 非常容易使用。其具体使用可以参看JSON框架总结。
其他的框架不再介绍,这里我们介绍Hessian的简单使用,实际上 Dubbo 采用的就是 Hessian 序列化来实现,只不过 Dubbo 对 Hessian 进行了重构,性能更高.Hessian是一个跨语言传输的二进制序列化协议,相对于Java默认的序列化机制,Hessian具有姮好的性能和易用性而且支持多种不同的语言,其中AbstractSerializerFactory,AbstractHessianOutput,AbstractSerializer,AbstractHessianInput,AbstractDeserializer是其核心类,下面我们使用一个例子介绍Hessian的使用:
public byte[] serizlize(T obj) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(outputStream);
hessianOutput.writeObject(obj);
return outputStream.toByteArray();
}
public T unserizlize(byte[] data) throws ClassNotFoundException, IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
HessianInput input = new HessianInput(inputStream);
return (T) input.readObject();
}