面试官:兄弟,说说你对transient的理解和感悟
素小暖:what?还有感悟?
Java序列化是指把Java对象转换为字节序列的过程。
Java反序列化是指把字节序列恢复为Java对象的过程。
在传递和保存对象时,保存对象的完整性和可传递性。
对象转换为有序字节流,可以在网络上传输或者保存在本地文件(一般json/xml文件居多)中。
根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。
(1)将对象转为字节流存储到硬盘上,当JVM停机的时候,字节流还会在硬盘上等待,等待下一次JVM启动,把序列化的对象,通过反序列化转为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)。
(2)序列化为字节流形式的对象可以进行网络传输(二进制形式)。
(3)通过序列化可以在进程间传递对象。
无法跨语言是Java序列化最致命的问题。
对于跨进程的服务调用,服务提供者可能是Java之外的其它语言,当我们需要和其它语言交互时,Java序列化就难以胜任。
事实上,目前几乎所有流行的Java RPC通信框架,都没有使用Java序列化作为编解码框架,原因就是它无法跨语言,而这些RPC框架往往需要支持跨语言调用。
Java RPC通信框架简介
RPC是远程过程调用的简称,广泛应用在大规模分布式应用中,作用是有助于系统的垂直拆分,使系统更易扩展。Java中RPC框架比较多,各有特色,广泛使用的有RMI、Hession、Dubbo等。
1、RMI(远程方法调用):
JAVA自带的远程方法调用工具,不过有一定的局限性,毕竟是JAVA语言最开始时的设计,后来很多框架的原理都基于RMI。
2、Hessian(基于HTTP的远程方法调用):
基于HTTP协议传输,在性能方面还不够完美,负载均衡和失效转移依赖于应用的负载均衡器,Hessian的使用则与RMI类似,区别在于淡化了Registry的角色,通过显示的地址调用,利用HessianProxyFactory根据配置的地址create一个代理对象,另外还要引入Hessian的Jar包。
3、Dubbo(淘宝开源的基于TCP的RPC框架)
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
1、分布式传递对象,或者网络传输,需要序列化
2、我调用你的jvm的方法,结果返回到我的jvm上进行处理
3、序列化可以保持对象的状态
比如:tomcat关闭以后会把session对象序列化到SESSIONS.ser文件中,等下次启动的时候就把这些session再加载到内存里面来。
4、数据传输并复原
在j2ee中页面与后台使用的比较多。尤其是在列表中的时候使用尤为突出。
比如:一个人员的列表保存起来的话,你可以将这个列表序列化,传到后台,然后再反序列化成person对象直接进行对象的保存。
5、比如EJB远程调用 分布式存储,缓存存储等
6、像银行卡、密码这些字段不能被序列化
实现 Serializable 接口:可以自定义 writeObject、readObject、writeReplace、readResolve 方法,会通过反射调用。
实现 Externalizable 接口:需要实现 writeExternal 和 readExternal 方法。
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。
序列化时不保存静态变量,这是因为序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。
transient代表对象的临时数据。
如果你不想让对象中的某个成员被序列化可以在定义它的时候加上 transient 关键字进行修饰,这样,在对象被序列化时其就不会被序列化。
transient 修饰过的成员反序列化后将赋予默认值,即 0 或 null。
有些时候像银行卡号这些字段是不希望在网络上传输的,transient的作用就是把这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
当一个父类实现序列化,子类自动实现序列化;而子类实现了 Serializable 接口,父类也需要实现Serializable 接口。
(1)安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;
(2)资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现;
如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存,这是能用序列化解决深拷贝的重要原因。
package javase.transientpackage;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password;
//construct、setter、getter
}
package javase.transientpackage;
import java.io.*;
public class TransientTest {
public static void main(String[] args) {
try {
SerializeUser();
DeSerializeUser();
} catch (IOException e) {
e.printStackTrace();
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//序列化
private static void SerializeUser() throws IOException{
User user = new User();
user.setUsername("素小暖");
user.setPassword("123456");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://data.txt"));
oos.writeObject(user);
oos.close();
System.out.println("普通字段序列化:username= "+user.getUsername());
System.out.println("添加了transient关键字序列化:password= "+user.getPassword());
}
//反序列化
private static void DeSerializeUser() throws IOException, ClassNotFoundException {
File file = new File("D://data.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User user = (User)ois.readObject();
System.out.println("普通字段反序列化:username= "+user.getUsername());
System.out.println("添加了transient关键字反序列化:password= "+user.getPassword());
}
}