当前位置 博文首页 > 沉淀所有的痛:transient的作用及序列化

    沉淀所有的痛:transient的作用及序列化

    作者:沉淀所有的痛 时间:2021-02-19 16:34

    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();     
    
    1. 序列化
    • 在调用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]);
        }
    

      

    bk