transient的作用及序列化
1.transient 介绍
Java中的transient关键字,transient是短暂的意思。对于transient 修饰的成员变量,在类的实例对象的序列化处理过程中会被忽略。 因此,transient变量不会贯穿对象的序列化和反序列化,生命周期仅存于调用者的内存中而不会写到磁盘里进行持久化。
(1)序列化
Java中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息,一个序列化后的对象可以被写到数据库或文件中,也可用于网络传输。一般地,当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或远程调用rpc(网络传输)的时候,经常需要让实体类实现Serializable接口,目的就是为了让其可序列化。当然,序列化后的最终目的是为了反序列化,恢复成原先的Java对象实例。所以序列化后的字节序列都是可以恢复成Java对象的,这个过程就是反序列化。
(2)为什么要用transient关键字?
在持久化对象时,对于一些特殊的数据成员(如用户的密码,银行卡号等),我们不想用序列化机制来保存它。为了在一个特定对象的一个成员变量上关闭序列化,可以在这个成员变量前加上关键字transient。
(3)transient的作用
transient是Java语言的关键字,用来表示一个成员变量不是该对象序列化的一部分。当一个对象被序列化的时候,transient型变量的值不包括在序列化的结果中。而非transient型的变量是被包括进去的。 注意static修饰的静态变量天然就是不可序
2. transient 使用总结
(1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法被访问。
(2) transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
(3)一个静态变量不管是否被transient修饰,均不能被序列化(如果反序列化后类中static变量还有值,则值为当前JVM中对应static变量的值)。序列化保存的是对象状态,静态变量保存的是类状态,因此序列化并不保存静态变量。
3. 序列化及反序列化
- 序列化
是指将Java对象保存为二进制字节码的过程。
- 反序列化
将二进制字节码重新转成Java对象的过程。
(1)为什么序列化
- 我们知道,一般Java对象的生命周期比Java虚拟机短,而实际的开发中,我们需要
在Jvm停止后能够继续持有对象,这个时候就需要用到序列化技术将对象持久到磁盘或数据库。
- 在多个项目进行RPC调用的,需要在网络上传输JavaBean对象。我们知道数据只能以二进制的
形式才能在网络上进行传输。所以也需要用到序列化技术。
(2)序列化的底层原理
1. 程序入口:
writeObject(obj)
Student stu1 = new Student(1001, "jack", "play"); Student stu2 = new Student(1002, "tom", "sleep"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\stu.dat")); oos.writeObject(stu1); oos.writeObject(stu2); oos.close();
- 序列化
- 在调用writeObject()方法之前,会先调用ObjectOutputStream的构造函数,生成
一个ObjectOutputStream对象。
public ObjectOutputStream(OutputStream out) throws IOException { verifySubclass(); // bout是底层的数据字节容器 bout = new BlockDataOutputStream(out); handles = new HandleTable(10, (float) 3.00); subs = new ReplaceTable(10, (float) 3.00); enableOverride = false; // 写入序列化文件头 writeStreamHeader(); // 设置文件缓存刷新配置 bout.setBlockDataMode(true); if (extendedDebugInfo) { debugInfoStack = new DebugTraceInfoStack(); } else { debugInfoStack = null; } }
writeStreamHeader的方法内容如下:
protected void writeStreamHeader() throws IOException { bout.writeShort(STREAM_MAGIC); bout.writeShort(STREAM_MAGIC); }
3. 调用writeObject()方法进行具体的序列化写入
public final void writeObject(Object obj) throws IOException { if (enableOverride) { writeObjectOverride(obj); return; } try { writeObject0(obj, false); } catch (IOException ex) { if (depth == 0) { writeFatalException(ex); } throw ex; } }
- writeObject0的具体内容
private void writeObject0(Object obj, boolean unshared) throws IOException { boolean oldMode = bout.setBlockDataMode(false); depth++; try { // handle previously written and non-replaceable objects int h; if ((obj = subs.lookup(obj)) == null) { writeNull(); return; } else if (!unshared && (h = handles.lookup(obj)) != -1) { writeHandle(h); return; } else if (obj instanceof Class) { writeClass((Class) obj, unshared); return; } else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared); return; } // check for replacement object Object orig = obj; // 需要序列的对象的Class对象 Class<?> cl = obj.getClass(); ObjectStreamClass desc; for (;;) { // 提示:跳过检查string和数组 // REMIND: skip this check for strings/arrays? Class<?> repCl; // 创建描述c1的ObjectStreamClass对象 desc = ObjectStreamClass.lookup(cl, true); if (!desc.hasWriteReplaceMethod() || (obj = desc.invokeWriteReplace(obj)) == null || (repCl = obj.getClass()) == cl) { break; } cl = repCl; } if (enableReplace) { Object rep = replaceObject(obj); if (rep != obj && rep != null) { cl = rep.getClass(); desc = ObjectStreamClass.lookup(cl, true); } obj = rep; } // if object replaced, run through original checks a second time if (obj != orig) { subs.assign(orig, obj); if (obj == null) { writeNull(); return; } else if (!unshared && (h = handles.lookup(obj)) != -1) { writeHandle(h); return; } else if (obj instanceof Class) { writeClass((Class) obj, unshared); return; } else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared); return; } } // remaining cases // 根据实际要写入的类型,进行不同的写入操作 // 由此可以看出String、Array、Enum是直接写入操作的 if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { // 实现序列化接口的都会执行下面的方法 // 从这里也可以看出Serializable是一个标记接口,其本身并没有什么意义 writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } } finally { depth--; bout.setBlockDataMode(oldMode); } }
从上面可以看出主要做了两件事
- 创建了ObjectStreamClass对象
- 根据实际要写入的类型,进行不同的写入操作
writeOrdinaryObject()
为什么说序列化并不安全
因为序列化的对象数据转换为二进制,并且完全可逆。但是在RMI调用时
所有private字段的数据都以明文二进制的形式出现在网络的套接字上,这显然是不安全的
解决方案:
- 1、 序列化Hook化(移位和复位)
- 2、 序列数据加密和签名
- 3、 利用transient的特性解决
- 4、 打包和解包代理
4. transient 在序列化底层的应用
static和transient修饰的字段不能被序列化。
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) { Field[] clFields = cl.getDeclaredFields(); ArrayList<ObjectStreamField> list = new ArrayList<>(); 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]); }