• 设计模式课程 设计模式精讲 8-6 单例设计模式-序列化破坏单例模式原理解析及解决方案


    课程地址:

    1    原理解析

    1.1  通过反射创建对象,序列化和反序列化把单例模式破坏了

    1.2  什么是序列化和反序列化

    2    代码演练

    2.1  序列化后的文件后源文件不是同一个对象(代码演练)

    2.2  序列化后的文件后源文件不是同一个对象解决方案(代码演练)

    2.3  序列化后的文件后源文件不是同一个对象(原理解析)

    2.4  序列化后的文件后源文件不是同一个对象解决方案(原理解析

    1    原理解析
    1.1 通过反射创建对象,序列化和反序列化把单例模式破坏了

    所以,在序列化和反序列化(把对象写入文件,从文件读取该对象)的时候,序列化和反序列化后的对象和之前的对象的hash码不再相同,不要再使用equals和==方法,如果想使用,请使用2.2的方法。具体原理参见视频。

    1.2  什么是序列化和反序列化

    序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。反序列化是相反的过程。

    自己理解:序列化就是将对象写入到文件等形式存储起来,反序列化是通过文件读取到对象。

    2    代码演练
    2.1  序列化后的文件后源文件不是同一个对象(代码演练)

    测试类:

    package com.geely.design.pattern.creational.singleton;
    
    import java.io.*;
    
    public class Test {
    
        /*public static void main(String [] args){
            //这样写异常,因为构造方法私有
    //        LazySingleton lazySingleton = new LazySingleton();
           LazySingleton lazySingleton = LazySingleton.getInstance();
           System.out.println(lazySingleton);
        }*/
    
    /*    public static void main(String [] args){
            Thread thread1 = new Thread(new T());
            Thread thread2 = new Thread(new T());
            thread1.start();
            thread2.start();
            System.out.println("结束了!!!");
        }*/
    
        /**
         * 序列化代码演练
         * 将 HungrySingleton对象放入文件,再从文件读取该对象,还是同一个对象吗?
         * 实际应用:在从文件存入后读取,用equals时需要注意(equals比较的是hash码)
         * @param args
         */
        public static void main(String [] args){
            try {
                //将singleton对象写入到输出流中
                HangrySingleton instance = HangrySingleton.getInstance();
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
                oos.writeObject(instance);
    
                //从输入流中读取到该对象
                File file = new File("singleton_file");
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                HangrySingleton instance2 = (HangrySingleton) ois.readObject();
                System.out.println(instance);
                System.out.println(instance2);
                System.out.println(instance==instance2);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
    }

    实体类:

    package com.geely.design.pattern.creational.singleton;
    
    
    
    public class HangrySingleton{
    
    
        /**
         * 声明私有常量,当类初始化的时候就已经赋值了。饿汉式在类初始化的时候只加载一次。
         * 所以也不会存在多线程的问题。
         */
        private final static HangrySingleton hangrySingleton;
    
        static {
            hangrySingleton= new HangrySingleton();
        }
    
        /**
         * 声明私有构造方法
         */
        private HangrySingleton(){
    
        }
    
       
    
        /**
         * 提供对外接口,获得对象
         * @return
         */
        public static HangrySingleton getInstance(){
            return hangrySingleton;
        }
    }

    打印日志:

    "C:Program FilesJavajdk1.7.0_79injava.exe" "-javaagent:D:javadevolopKitideaanZhIntelliJ IDEA Community Edition 2018.1.4libidea_rt.jar=22216:D:javadevolopKitideaanZhIntelliJ IDEA Community Edition 2018.1.4in" -Dfile.encoding=UTF-8 -classpath "C:Program FilesJavajdk1.7.0_79jrelibcharsets.jar;C:Program FilesJavajdk1.7.0_79jrelibdeploy.jar;C:Program FilesJavajdk1.7.0_79jrelibextaccess-bridge-64.jar;C:Program FilesJavajdk1.7.0_79jrelibextdnsns.jar;C:Program FilesJavajdk1.7.0_79jrelibextjaccess.jar;C:Program FilesJavajdk1.7.0_79jrelibextlocaledata.jar;C:Program FilesJavajdk1.7.0_79jrelibextsunec.jar;C:Program FilesJavajdk1.7.0_79jrelibextsunjce_provider.jar;C:Program FilesJavajdk1.7.0_79jrelibextsunmscapi.jar;C:Program FilesJavajdk1.7.0_79jrelibextzipfs.jar;C:Program FilesJavajdk1.7.0_79jrelibjavaws.jar;C:Program FilesJavajdk1.7.0_79jrelibjce.jar;C:Program FilesJavajdk1.7.0_79jrelibjfr.jar;C:Program FilesJavajdk1.7.0_79jrelibjfxrt.jar;C:Program FilesJavajdk1.7.0_79jrelibjsse.jar;C:Program FilesJavajdk1.7.0_79jrelibmanagement-agent.jar;C:Program FilesJavajdk1.7.0_79jrelibplugin.jar;C:Program FilesJavajdk1.7.0_79jrelib
    esources.jar;C:Program FilesJavajdk1.7.0_79jrelib
    t.jar;F:xiangmu3XinIdeadesign_pattern	argetclasses" com.geely.design.pattern.creational.singleton.Test
    java.io.NotSerializableException: com.geely.design.pattern.creational.singleton.HangrySingleton
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1183)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:347)
        at com.geely.design.pattern.creational.singleton.Test.main(Test.java:33)
    
    Process finished with exit code 0
    2.2  序列化后的文件后源文件不是同一个对象解决方案(代码演练)

    测试类:

    package com.geely.design.pattern.creational.singleton;
    
    import java.io.*;
    
    public class Test {
    
        /*public static void main(String [] args){
            //这样写异常,因为构造方法私有
    //        LazySingleton lazySingleton = new LazySingleton();
           LazySingleton lazySingleton = LazySingleton.getInstance();
           System.out.println(lazySingleton);
        }*/
    
    /*    public static void main(String [] args){
            Thread thread1 = new Thread(new T());
            Thread thread2 = new Thread(new T());
            thread1.start();
            thread2.start();
            System.out.println("结束了!!!");
        }*/
    
        /**
         * 序列化代码演练
         * 将 HungrySingleton对象放入文件,再从文件读取该对象,还是同一个对象吗?
         * 实际应用:在从文件存入后读取,用equals时需要注意(equals比较的是hash码)
         * @param args
         */
        public static void main(String [] args){
            try {
                //将singleton对象写入到输出流中
                HangrySingleton instance = HangrySingleton.getInstance();
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
                oos.writeObject(instance);
    
                //从输入流中读取到该对象
                File file = new File("singleton_file");
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                HangrySingleton instance2 = (HangrySingleton) ois.readObject();
                System.out.println(instance);
                System.out.println(instance2);
                System.out.println(instance==instance2);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
    }

    实体类:

    package com.geely.design.pattern.creational.singleton;
    
    import java.io.Serializable;
    
    public class HangrySingleton implements Serializable {
    
    
        /**
         * 声明私有常量,当类初始化的时候就已经赋值了。饿汉式在类初始化的时候只加载一次。
         * 所以也不会存在多线程的问题。
         */
        private final static HangrySingleton hangrySingleton;
    
        static {
            hangrySingleton= new HangrySingleton();
        }
    
        /**
         * 声明私有构造方法
         */
        private HangrySingleton(){
    
        }
    
        /**
         *
         */
        private Object readResolve(){
            return hangrySingleton;
        }
    
        /**
         * 提供对外接口,获得对象
         * @return
         */
        public static HangrySingleton getInstance(){
            return hangrySingleton;
        }
    }

    打印日志:

    "C:Program FilesJavajdk1.7.0_79injava.exe" "-javaagent:D:javadevolopKitideaanZhIntelliJ IDEA Community Edition 2018.1.4libidea_rt.jar=22084:D:javadevolopKitideaanZhIntelliJ IDEA Community Edition 2018.1.4in" -Dfile.encoding=UTF-8 -classpath "C:Program FilesJavajdk1.7.0_79jrelibcharsets.jar;C:Program FilesJavajdk1.7.0_79jrelibdeploy.jar;C:Program FilesJavajdk1.7.0_79jrelibextaccess-bridge-64.jar;C:Program FilesJavajdk1.7.0_79jrelibextdnsns.jar;C:Program FilesJavajdk1.7.0_79jrelibextjaccess.jar;C:Program FilesJavajdk1.7.0_79jrelibextlocaledata.jar;C:Program FilesJavajdk1.7.0_79jrelibextsunec.jar;C:Program FilesJavajdk1.7.0_79jrelibextsunjce_provider.jar;C:Program FilesJavajdk1.7.0_79jrelibextsunmscapi.jar;C:Program FilesJavajdk1.7.0_79jrelibextzipfs.jar;C:Program FilesJavajdk1.7.0_79jrelibjavaws.jar;C:Program FilesJavajdk1.7.0_79jrelibjce.jar;C:Program FilesJavajdk1.7.0_79jrelibjfr.jar;C:Program FilesJavajdk1.7.0_79jrelibjfxrt.jar;C:Program FilesJavajdk1.7.0_79jrelibjsse.jar;C:Program FilesJavajdk1.7.0_79jrelibmanagement-agent.jar;C:Program FilesJavajdk1.7.0_79jrelibplugin.jar;C:Program FilesJavajdk1.7.0_79jrelib
    esources.jar;C:Program FilesJavajdk1.7.0_79jrelib
    t.jar;F:xiangmu3XinIdeadesign_pattern	argetclasses" com.geely.design.pattern.creational.singleton.Test
    com.geely.design.pattern.creational.singleton.HangrySingleton@5c67aece
    com.geely.design.pattern.creational.singleton.HangrySingleton@5c67aece
    true
    
    Process finished with exit code 0
    2.3  序列化后的文件后源文件不是同一个对象(原理解析)

    test.java

        /**
         * 序列化代码演练
         * 将 HungrySingleton对象放入文件,再从文件读取该对象,还是同一个对象吗?
         * 实际应用:在从文件存入后读取,用equals时需要注意(equals比较的是hash码)
         * @param args
         */
        public static void main(String [] args){
            try {
                //将singleton对象写入到输出流中
                HangrySingleton instance = HangrySingleton.getInstance();
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
                oos.writeObject(instance);
    
                //从输入流中读取到该对象
                File file = new File("singleton_file");
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                HangrySingleton instance2 = (HangrySingleton) ois.readObject();
                System.out.println(instance);
                System.out.println(instance2);
                System.out.println(instance==instance2);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

    ObjectInputStream.java

     public final Object readObject()
            throws IOException, ClassNotFoundException
        {
            if (enableOverride) {
                return readObjectOverride();
            }
    
            // if nested read, passHandle contains handle of enclosing object
            int outerHandle = passHandle;
            try {
                Object obj = readObject0(false);
                handles.markDependency(outerHandle, passHandle);
                ClassNotFoundException ex = handles.lookupException(passHandle);
                if (ex != null) {
                    throw ex;
                }
                if (depth == 0) {
                    vlist.doCallbacks();
                }
                return obj;
            } finally {
                passHandle = outerHandle;
                if (closed && depth == 0) {
                    clear();
                }
            }
        }
     /**
         * Underlying readObject implementation.
         */
        private Object readObject0(boolean unshared) throws IOException {
            boolean oldMode = bin.getBlockDataMode();
            if (oldMode) {
                int remain = bin.currentBlockRemaining();
                if (remain > 0) {
                    throw new OptionalDataException(remain);
                } else if (defaultDataEnd) {
                    /*
                     * Fix for 4360508: stream is currently at the end of a field
                     * value block written via default serialization; since there
                     * is no terminating TC_ENDBLOCKDATA tag, simulate
                     * end-of-custom-data behavior explicitly.
                     */
                    throw new OptionalDataException(true);
                }
                bin.setBlockDataMode(false);
            }
    
            byte tc;
            while ((tc = bin.peekByte()) == TC_RESET) {
                bin.readByte();
                handleReset();
            }
    
            depth++;
            try {
                switch (tc) {
                    case TC_NULL:
                        return readNull();
    
                    case TC_REFERENCE:
                        return readHandle(unshared);
    
                    case TC_CLASS:
                        return readClass(unshared);
    
                    case TC_CLASSDESC:
                    case TC_PROXYCLASSDESC:
                        return readClassDesc(unshared);
    
                    case TC_STRING:
                    case TC_LONGSTRING:
                        return checkResolve(readString(unshared));
    
                    case TC_ARRAY:
                        return checkResolve(readArray(unshared));
    
                    case TC_ENUM:
                        return checkResolve(readEnum(unshared));
    
                    case TC_OBJECT:
                        return checkResolve(readOrdinaryObject(unshared));
    
                    case TC_EXCEPTION:
                        IOException ex = readFatalException();
                        throw new WriteAbortedException("writing aborted", ex);
    
                    case TC_BLOCKDATA:
                    case TC_BLOCKDATALONG:
                        if (oldMode) {
                            bin.setBlockDataMode(true);
                            bin.peek();             // force header read
                            throw new OptionalDataException(
                                bin.currentBlockRemaining());
                        } else {
                            throw new StreamCorruptedException(
                                "unexpected block data");
                        }
    
                    case TC_ENDBLOCKDATA:
                        if (oldMode) {
                            throw new OptionalDataException(true);
                        } else {
                            throw new StreamCorruptedException(
                                "unexpected end of block data");
                        }
    
                    default:
                        throw new StreamCorruptedException(
                            String.format("invalid type code: %02X", tc));
                }
            } finally {
                depth--;
                bin.setBlockDataMode(oldMode);
            }
        }

    3

        /**
         * Reads and returns "ordinary" (i.e., not a String, Class,
         * ObjectStreamClass, array, or enum constant) object, or null if object's
         * class is unresolvable (in which case a ClassNotFoundException will be
         * associated with object's handle).  Sets passHandle to object's assigned
         * handle.
         */
        private Object readOrdinaryObject(boolean unshared)
            throws IOException
        {
            if (bin.readByte() != TC_OBJECT) {
                throw new InternalError();
            }
    
            ObjectStreamClass desc = readClassDesc(false);
            desc.checkDeserialize();
    
            Class<?> cl = desc.forClass();
            if (cl == String.class || cl == Class.class
                    || cl == ObjectStreamClass.class) {
                throw new InvalidClassException("invalid class descriptor");
            }
    
            Object obj;
            try {
                obj = desc.isInstantiable() ? desc.newInstance() : null;//如果 desc.isInstantiable() 为true,通过反射返回新的对象,否则返回null,由下可知返回true
            } catch (Exception ex) {
                throw (IOException) new InvalidClassException(
                    desc.forClass().getName(),
                    "unable to create instance").initCause(ex);
            }
    
            passHandle = handles.assign(unshared ? unsharedMarker : obj);
            ClassNotFoundException resolveEx = desc.getResolveException();
            if (resolveEx != null) {
                handles.markException(passHandle, resolveEx);
            }
    
            if (desc.isExternalizable()) {
                readExternalData((Externalizable) obj, desc);
            } else {
                readSerialData(obj, desc);
            }
    
            handles.finish(passHandle);
    
            if (obj != null &&
                handles.lookupException(passHandle) == null &&
                desc.hasReadResolveMethod())
            {
                Object rep = desc.invokeReadResolve(obj);
                if (unshared && rep.getClass().isArray()) {
                    rep = cloneArray(rep);
                }
                if (rep != obj) {
                    handles.setObject(passHandle, obj = rep);
                }
            }
    
            return obj;
        }
        /**
         * Returns true if represented class is serializable/externalizable and can
         * be instantiated by the serialization runtime--i.e., if it is
         * externalizable and defines a public no-arg constructor, or if it is
         * non-externalizable and its first non-serializable superclass defines an
         * accessible no-arg constructor.  Otherwise, returns false.
    * 如果当前类是序列化的或者externalizable, 并且可以在运行时实例化,则会返回true
    */ boolean isInstantiable() { return (cons != null); }

    结论:

    通过反射创建的对象是新的对象,不是原来的对象。

    2.4  序列化后的文件后源文件不是同一个对象解决方案(原理解析

    3

        /**
         * Reads and returns "ordinary" (i.e., not a String, Class,
         * ObjectStreamClass, array, or enum constant) object, or null if object's
         * class is unresolvable (in which case a ClassNotFoundException will be
         * associated with object's handle).  Sets passHandle to object's assigned
         * handle.
         */
        private Object readOrdinaryObject(boolean unshared)
            throws IOException
        {
            if (bin.readByte() != TC_OBJECT) {
                throw new InternalError();
            }
    
            ObjectStreamClass desc = readClassDesc(false);
            desc.checkDeserialize();
    
            Class<?> cl = desc.forClass();
            if (cl == String.class || cl == Class.class
                    || cl == ObjectStreamClass.class) {
                throw new InvalidClassException("invalid class descriptor");
            }
    
            Object obj;
            try {
                obj = desc.isInstantiable() ? desc.newInstance() : null;//如果 desc.isInstantiable() 为true,通过反射返回新的对象,否则返回null,由下可知返回true
            } catch (Exception ex) {
                throw (IOException) new InvalidClassException(
                    desc.forClass().getName(),
                    "unable to create instance").initCause(ex);
            }
    
            passHandle = handles.assign(unshared ? unsharedMarker : obj);
            ClassNotFoundException resolveEx = desc.getResolveException();
            if (resolveEx != null) {
                handles.markException(passHandle, resolveEx);
            }
    
            if (desc.isExternalizable()) {
                readExternalData((Externalizable) obj, desc);
            } else {
                readSerialData(obj, desc);
            }
    
            handles.finish(passHandle);
    
            if (obj != null &&
                handles.lookupException(passHandle) == null &&
                desc.hasReadResolveMethod())
            {
                Object rep = desc.invokeReadResolve(obj);
                if (unshared && rep.getClass().isArray()) {
                    rep = cloneArray(rep);
                }
                if (rep != obj) {
                    handles.setObject(passHandle, obj = rep);
                }
            }
    
            return obj;
        }

    ObjectStreamClass.java:

        /**
         * Returns true if represented class is serializable or externalizable and
         * defines a conformant readResolve method.  Otherwise, returns false.
    * 如果当前类是序列化的并且定义了readResolve 方法,则会返回true,否则,返回false
    * 所以最终返回true
    */ boolean hasReadResolveMethod() { return (readResolveMethod != null); }

    ObjectStreamClass.java:定义readResolve方法:

      /**
         * Creates local class descriptor representing given class.
         */
        private ObjectStreamClass(final Class<?> cl) {
            this.cl = cl;
            name = cl.getName();
            isProxy = Proxy.isProxyClass(cl);
            isEnum = Enum.class.isAssignableFrom(cl);
            serializable = Serializable.class.isAssignableFrom(cl);
            externalizable = Externalizable.class.isAssignableFrom(cl);
    
            Class<?> superCl = cl.getSuperclass();
            superDesc = (superCl != null) ? lookup(superCl, false) : null;
            localDesc = this;
    
            if (serializable) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        if (isEnum) {
                            suid = Long.valueOf(0);
                            fields = NO_FIELDS;
                            return null;
                        }
                        if (cl.isArray()) {
                            fields = NO_FIELDS;
                            return null;
                        }
    
                        suid = getDeclaredSUID(cl);
                        try {
                            fields = getSerialFields(cl);
                            computeFieldOffsets();
                        } catch (InvalidClassException e) {
                            serializeEx = deserializeEx =
                                new ExceptionInfo(e.classname, e.getMessage());
                            fields = NO_FIELDS;
                        }
    
                        if (externalizable) {
                            cons = getExternalizableConstructor(cl);
                        } else {
                            cons = getSerializableConstructor(cl);
                            writeObjectMethod = getPrivateMethod(cl, "writeObject",
                                new Class<?>[] { ObjectOutputStream.class },
                                Void.TYPE);
                            readObjectMethod = getPrivateMethod(cl, "readObject",
                                new Class<?>[] { ObjectInputStream.class },
                                Void.TYPE);
                            readObjectNoDataMethod = getPrivateMethod(
                                cl, "readObjectNoData", null, Void.TYPE);
                            hasWriteObjectData = (writeObjectMethod != null);
                        }
                        writeReplaceMethod = getInheritableMethod(
                            cl, "writeReplace", null, Object.class);
                        readResolveMethod = getInheritableMethod(
                            cl, "readResolve", null, Object.class);
                        return null;
                    }
                });
            } else {
                suid = Long.valueOf(0);
                fields = NO_FIELDS;
            }
    
            try {
                fieldRefl = getReflector(fields, this);
            } catch (InvalidClassException ex) {
                // field mismatches impossible when matching local fields vs. self
                throw new InternalError();
            }
    
            if (deserializeEx == null) {
                if (isEnum) {
                    deserializeEx = new ExceptionInfo(name, "enum type");
                } else if (cons == null) {
                    deserializeEx = new ExceptionInfo(name, "no valid constructor");
                }
            }
            for (int i = 0; i < fields.length; i++) {
                if (fields[i].getField() == null) {
                    defaultSerializeEx = new ExceptionInfo(
                        name, "unmatched serializable field(s) declared");
                }
            }
        }
    /**
         * Returns non-static, non-abstract method with given signature provided it
         * is defined by or accessible (via inheritance) by the given class, or
         * null if no match found.  Access checks are disabled on the returned
         * method (if any).
         */
        private static Method getInheritableMethod(Class<?> cl, String name,
                                                   Class<?>[] argTypes,
                                                   Class<?> returnType)
        {
            Method meth = null;
            Class<?> defCl = cl;
            while (defCl != null) {
                try {
                    meth = defCl.getDeclaredMethod(name, argTypes);
                    break;
                } catch (NoSuchMethodException ex) {
                    defCl = defCl.getSuperclass();
                }
            }
    
            if ((meth == null) || (meth.getReturnType() != returnType)) {
                return null;
            }
            meth.setAccessible(true);
            int mods = meth.getModifiers();
            if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0) {
                return null;
            } else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
                return meth;
            } else if ((mods & Modifier.PRIVATE) != 0) {
                return (cl == defCl) ? meth : null;
            } else {
                return packageEquals(cl, defCl) ? meth : null;
            }
        

    Class.java

        @CallerSensitive
        public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException {
            // be very careful not to change the stack depth of this
            // checkMemberAccess call for security reasons
            // see java.lang.SecurityManager.checkMemberAccess
            checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
            Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
            if (method == null) {
                throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
            }
            return method;
        }

    结论:

    在底层类中,搜索 实现序列化接口的类是否有readResolve方法,有的话,就new一个新对象,否则,返回null。

  • 相关阅读:
    优化C/C++代码的小技巧
    闭包,看这一篇就够了——带你看透闭包的本质,百发百中
    7215:简单的整数划分问题
    常见问题最佳实践三:服务启动顺序
    JAVA 用分苹果来理解本题
    arcgis访问格式
    墨卡托投影
    C# 从DataTable中取值
    Base64编码的字符串与图片的转换 C#
    墨卡托投影实现
  • 原文地址:https://www.cnblogs.com/1446358788-qq/p/11374488.html
Copyright © 2020-2023  润新知