Java领域的对象如何传输
基于socket进行对象传输
当对象没有被序列化时,报异常(java.io.NotSerializableException: com.my.User
);通过对象实现 Serializable 接口就可以解决这个问题。
了解序列化的意义
序列化把对象-->可存储或可传输的过程,即对象-->字节数组,解决jvm的对象的持久化或跨服务传输的问题。
反序列化则相反
序列化的高阶认识
java原生序列化
通过ObjectOutputStream和ObjectInputStream来实现序列化和反序列化
public <T> byte[] serialize(T obj) {
ByteArrayOutputStream byteArrayOutputStream=
new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream=
new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(obj);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
public <T> T deserialize(byte[] data, Class<T> clazz) {
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(data);
try {
ObjectInputStream objectInputStream=
new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
serialVersionUID 的作用
作用
来验证版本的一致性,反序列化serialVersionUID和实体类的serialVersionUID,若一致则可以进行反序列化,若不一致,则抛出异常InvalidCastException如这种场景:隐式声明序列化+序列化;修改本地实体类的内容;再进行反序列化会抛出InvalidCastException。
生成方法
1 隐式:编译器会默认生成一个
2 显示:private static final long serialVersionUID
生成规则
根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段即是序列化,类似于指纹算法,只要内容发生修改,则UID就不一样。
Transient 关键字
若变量加上Transient,则阻止该变量序列化,在被反序列化后,变量会被初始化默认值。
绕开 transient 机制的办法
通过增加writeObject和readObject的方法来解决,该方法在ObjectInputStream 和 ObjectOutputStream通过反射调用
public class User {
private static final long serialVersionUID = -434539422310062943L;
private String name;
private int age;
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeObject(name);
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
name=(String)s.readObject();
}
}
image.png
序列化总结
Java 序列化只是针对对象的状态进行保存,至于对象中的方法,序列化不关心
当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口
当一个对象的实例变量引用了其他对象,序列化这个对象的时候会自动把引用的对象也进行序列化(实现深度克隆)
当某个字段被申明为 transient 后,默认的序列化机制会忽略这个字段
被申明为 transient 的字段,如果需要序列化,可以添加两个私有方法writeObject 和readObject
分布式架构下常见序列化技术
序列化的评价标准
序列化后的数据小,从而使传输效率高
其他语言可以识别和对接
简单了解各种序列化技术
XML 序列化框架介绍
可读性,跨语言,但序列化之后的字节码文件较大,适合性能要求不高的系统。实现方式有 XStream 和 Java 自带的 XML 序列化和反序列化两种
public class XStreamSerializer implements ISerializer{
XStream xStream=new XStream(new DomDriver());
@Override
public <T> byte[] serialize(T obj) {
return xStream.toXML(obj).getBytes();
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
return (T)xStream.fromXML(new String(data));
}
}
JSON 序列化框架
可读性,跨语言且相对于xml,json序列化后的字节码文件较小,企业应用广泛
JSON 序列化常用的开源工具有很多
- Jackson (https://github.com/FasterXML/jackson)
- 阿里开源的 FastJson (https://github.com/alibaba/fastjon)
- Google 的 GSON (https://github.com/google/gson)
这几种 json 序列化工具中,Jackson 与 fastjson 要比 GSON 的性能要好,但是 Jackson、
GSON 的稳定性要比 Fastjson 好。而 fastjson 的优势在于提供的 api 非常容易使用
public class FastJsonSeriliazer implements ISerializer{
@Override
public <T> byte[] serialize(T obj) {
return JSON.toJSONString(obj).getBytes();
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
return (T)JSON.parseObject(new String(data),clazz);
}
}
Hessian 序列化框架
跨语言,性能更高的二进制序列化协议
public class HessianSerializer implements ISerializer{
@Override
public <T> byte[] serialize(T obj) {
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
HessianOutput hessianOutput=new HessianOutput(outputStream);
try {
hessianOutput.writeObject(obj);
return outputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
ByteArrayInputStream inputStream=new ByteArrayInputStream(data);
HessianInput hessianInput=new HessianInput(inputStream);
try {
return (T)hessianInput.readObject();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Protobuf 序列化框架
跨语言和序列化后的字节码的文件最小,性能最高。但使用起来比较麻烦。
Protobuf 序列化的原理
name=mic,age=300的序列化后的结果:10 3 77 105 99 16 -84 2
对数字的压缩
image.png把300变成:-84 、2
字符如何转化为编码
“Mic”这个字符,需要根据 ASCII 对照表转化为数字。
M =77、i=105、c=99
所以结果为 77 105 99
存储格式
protobuf 采用 T-L-V 作为存储方式
image.png
tag 的计算方式是 field_number(当前字段的编号) << 3 | wire_type
为什么空间如此小
对值进行压缩
没有了字段名,通过位置来知道字段名
序列化技术的选型
技术层面
序列化空间开销,也就是序列化产生的结果大小,这个影响到传输的性能
序列化过程中消耗的时长,序列化消耗时间过长影响到业务的响应时间
序列化协议是否支持跨平台,跨语言。因为现在的架构更加灵活,如果存在异构系统通信需求,那么这个是必须要考虑的
可扩展性/兼容性,在实际业务开发中,系统往往需要随着需求的快速迭代来实现快速更新,
这就要求我们采用的序列化协议基于良好的可扩展性/兼容性,比如在现有的序列化数据结构中新增一个业务字段,不会影响到现有的服务
技术的流行程度,越流行的技术意味着使用的公司多,那么很多坑都已经淌过并且得到了
解决,技术解决方案也相对成熟学习难度和易用性
选型建议
对性能要求不高的场景,可以采用基于 XML 的 SOAP 协议
对性能和间接性有比较高要求的场景,那么 Hessian、Protobuf、Thrift、Avro 都可以。
基于前后端分离,或者独立的对外的 api 服务,选用 JSON 是比较好的,对于调试、可读性都很不错
Avro 设计理念偏于动态类型语言,那么这类的场景使用 Avro 是可以的
网友评论