设计模式课程 设计模式精讲 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。

原文地址:https://www.cnblogs.com/1446358788-qq/p/11374488.html