单例设计模式-序列化破坏单例模式原理解析及解决方案

越来越成熟了,那是不是坚不可摧的呢,现在我们就要用序列号和反序列化来破坏单例模式,后面也会重点讲一下原理,

好好听,让我们来一起破坏单例模式吧,首先还是来到Test类里边
package com.learn.design.pattern.creational.singleton;

import java.io.Serializable;

/**
 * 我们让他来实现序列号接口
 * 具体序列话和反序列化的版本号呢
 * 我们就不关注了
 * 因为这个不是重点
 * 
 * 我们现在的HungrySingleton实现了Serializable接口
 * 那么在ObjectStreamClass这里边
 * 判断就会返回true
 * boolean isInstantiable() {
 *   requireInitialized();
 *   return (cons != null);
 * }
 * 返回true之后再回来
 * 
 * @author Leon.Sun
 *
 */
public class HungrySingleton implements Serializable,Cloneable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    
    private HungrySingleton(){
        if(hungrySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    /**
     * 我们写一个方法
     * 返回一个Object
     * 
     * 从override里面根本就没有
     * 他根本就不是Object的方法
     * 那方法为什么又叫readResolve呢
     * 叫别的名字可不可以呢
     * 那我们就较真
     * 为什么要这么写
     * 
     * 
     * @return
     */
    private Object readResolve(){
    	/**
    	 * 然后在这里面返回一个单例对象
    	 * 我们回到Test里边
    	 * 我们直接再run一下
    	 * 咱们一起来看
    	 * 最后看是否相等呢
    	 * 最后返回true
    	 * 这就很神奇了
    	 * 回到这个单例类里面
    	 * 你们肯定有一个疑问
    	 * 你为什么写这个方法啊
    	 * 
    	 * 
    	 * 
    	 */
        return hungrySingleton;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return getInstance();
    }    
}
package com.learn.design.pattern.creational.singleton;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 我们已经确定单例模式只能获取一个对象
 * 就使用HungrySingleton这个类作为测试
 * 
 * 
 * @author Leon.Sun
 *
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//        LazySingleton lazySingleton = LazySingleton.getInstance();

//        System.out.println("main thread"+ThreadLocalInstance.getInstance());
//        System.out.println("main thread"+ThreadLocalInstance.getInstance());
//        System.out.println("main thread"+ThreadLocalInstance.getInstance());
//        System.out.println("main thread"+ThreadLocalInstance.getInstance());
//        System.out.println("main thread"+ThreadLocalInstance.getInstance());
//        System.out.println("main thread"+ThreadLocalInstance.getInstance());

//        Thread t1 = new Thread(new T());
//        Thread t2 = new Thread(new T());
//        t1.start();
//        t2.start();
//        System.out.println("program end");

    	/**
    	 * 从这里面拿了一个对象来
    	 * 那这个单例对象我们拿到了
    	 * 现在我们想象一下
    	 * 如果我们把这个instance序列号到一个文件中
    	 * 然后再从文件里取出来
    	 * 那这两个对象还是同一个对象吗
    	 * 那现在我们就来测试一下
    	 * 
    	 * 首先单例获取一个对象
    	 * 
    	 * 
    	 */
        HungrySingleton instance = HungrySingleton.getInstance();
//        EnumInstance instance = EnumInstance.getInstance();
//        instance.setData(new Object());
//
        /**
         * 我们直接用ObjectOutputStream流
         * oos就是ObjectOutputStream三个字母的首字母小写
         * new一个ObjectOutputStream
         * 里面new一个FileOutputStream
         * 名字我们起一个名字就叫singleton_file
         * 这个时候这里爆红了
         * 应该是异常
         * 我们也不try catch了
         * 我们直接抛出
         * 然后呢继续
         * 
         * 看一下我们使用的类ObjectOutputStream
         * 
         * 然后序列号
         * 
         */
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        /**
         * 我们通过ObjectOutputStream对象写一下
         * 
         * 
         */
        oos.writeObject(instance);
//
        /**
         * 现在我们再读一下他
         * new一个File
         * 文件名singleton_file
         * 
         * 
         */
        File file = new File("singleton_file");
        /**
         * 我们再通过ObjectInputStream读取这个文件
         * 里面再放一个FileInputStream
         * 把这个file放进来
         * 这个时候我们通过ObjectInputStream获取的这个对象去读取他
         * 和上面声明的instance做一个对比
         * 判断他们是不是同一个对象
         * 
         * ObjectInputStream
         * 我们重点看一下ois这个对象
         * 也就是ObjectInputStream这个类的对象
         * 他的readObject方法
         * 我们进来看一下
         * 看一下这个方法
         * 从上往下看
         * 重点看一下Object obj = readObject0(false);这一行的代码
         * 他又调用readObject0这个方法
         * 我们进来看一下
         * private Object readObject0(boolean unshared)
         * 这个就是读取对象
         * 各种逻辑不是我们要讲的重点
         * 我们往下看
         * 这里面有一个switch
         * switch (tc)
         * 读对应的类型
         * TC_STRING
         * TC_LONGSTRING
         * TC_ARRAY
         * TC_ENUM
         * TC_OBJECT
         * 那我们的是什么呢
         * 肯定是Object
         * TC_OBJECT
         * 所以我们的代码会走到return checkResolve(readOrdinaryObject(unshared));这一行
         * 然后我们看一下他调的方法
         * 这一行调了两个方法
         * 首先调了readOrdinaryObject这个方法
         * 然后把返回值放入checkResolve方法
         * 我们先进入readOrdinaryObject这个方法
         * private Object readOrdinaryObject(boolean unshared)
         * 往下看
         * 上面还是各种判断校验
         * 这里可以看到
         * if (cl == String.class || cl == Class.class
         *       || cl == ObjectStreamClass.class) {
         *   throw new InvalidClassException("invalid class descriptor");
         * }
         * 如果是String,
         * Class或者ObjectClass
         * 就会报一个异常
         * 无效的class
         * Object obj;
         * 然后我们看一下obj = desc.isInstantiable() ? desc.newInstance() : null;这一行
         * 这里面做了一个判断
         * 首先我们看一下obj这个Object对象
         * 然后往下走
         * 看看他做什么用
         * return obj;
         * 我们看到把它返回回去了
         * 接着回来
         * 那我们就看这一行
         * obj = desc.isInstantiable() ? desc.newInstance() : null
         * 这里面做了一个判断
         * 如果isInstantiable这个返回的是true的话
         * 就会生成一个新的对象
         * 否则返回null
         * 这个就是从readObject传进来的
         * 所以我们看一下
         * isInstantiable()这个方法是做什么用的
         * 这里呢很简单
	     * boolean isInstantiable() {
	     *   requireInitialized();
	     *   return (cons != null);
	     * }
         * return cons不等于空
         * cons是什么呢
         * private Constructor<?> cons;
         * 也就是反射的构造器类型
         * public final class Constructor<T> extends Executable
         * 回来
         * 从这行代码看不出什么
         * 那这个时候我们就得看注释了
         * 还好我们有注释可以看
         * 这个注释意思是说
         * Returns true if represented class is serializable/externalizable and can
         * 我们看一下serializable/externalizable
         * 包括下面还有be instantiated by the serialization runtime--i.e., if it is
         * 简单的说呢
         * 如果serializable或者externalizable
         * 如果这样的一个类class
         * 正在运行时被实例化
         * 那么该方法就会返回true
         * 所以returns true
         * 那这两个序列化方式
         * serializable这个是比较全的
         * 而externalizable这个是定制哪些字段
         * 都可以通过它来定制
         * 那么看一下这个类
         * 他又继承serializable
         * 他作为接口又是serializable的子类
         * 而我们平时实现的都是serializable这个类
         * 那平时定制序列号的情况
         * 也比较少见
         * 那就好理解了
		 * 我们现在的HungrySingleton实现了Serializable接口
		 * 那么在ObjectStreamClass这里边
		 * 判断就会返回true
		 * boolean isInstantiable() {
		 *   requireInitialized();
		 *   return (cons != null);
		 * }
		 * 返回true之后再回来 
		 * obj = desc.isInstantiable() ? desc.newInstance() : null;
		 * isInstantiable他为true的话就会newInstance
		 * 然后把obj返回回去
		 * 到这儿就比较清晰了
		 * 这个对象是通过反射创建出来的对象
		 * newInstance
		 * ObjectStreamClass desc = readClassDesc(false);
		 * desc是ObjectStreamClass类型的
		 * 既然通过反射去创建对象
		 * 那肯定和之前的对象不是同一个
		 * 这也就是解释了我刚刚为什么序列化和反序列化单例模式破坏了
		 * 那到这里之后啊
		 * 还是没有找到我们想要的答案
		 * 我们接着往下看
         * if (obj != null &&
         *   handles.lookupException(passHandle) == null &&
         *   desc.hasReadResolveMethod()) 
		 * 判断obj不会为空
		 * 然后进行一系列的判断
		 * 然后深入进去看了
		 * 然后看看hasReadResolveMethod这个
		 * 从这个名字就能够看出来
		 * 判断它是否有ReadResolve方法
		 * 我们进来看一下
		 *    boolean hasReadResolveMethod() {
		 *       requireInitialized();
		 *       return (readResolveMethod != null);
		 *   }
		 * 这个也很简单
		 * 我们首先看一下这个对象
		 * 他就是一个Method
		 * private Method readResolveMethod;
		 * return (readResolveMethod != null);
		 * 也就是这一行通过源码看不出什么
		 * 那我们就看注释
		 * 注释很有用
		 * 平时看源码的时候一定要养成看注释的好习惯
		 * Returns true if represented class is serializable or externalizable and
		 * 返回true
		 * 如果这个类是serializable这个接口或者externalizable这个接口类型
		 * 实现了这个接口那class自然也是这个类型
		 * and还有一个条件
		 * defines a conformant readResolve method.  Otherwise, returns false.
		 * 那这个注释已经说得很清楚了
		 * 我们回来
		 * 因为哦我们定义了这个方法
		 * 所以desc.hasReadResolveMethod()这里判断剩下
		 * 那怎么调用那个方法呢
		 * Object rep = desc.invokeReadResolve(obj);
		 * 把obj传进来
		 * 进来看一下
		 * 很明显通过这个名字就能够看出来
		 * 它是通过反射来调用invokeReadResolve方法的
		 * return readResolveMethod.invoke(obj, (Object[]) null);
		 * 也就是说这个时候调用HungrySingleton里面写的readResolve方法
		 * 那么再回到ObjectStreamClass里边
		 * return readResolveMethod.invoke(obj, (Object[]) null);
		 * 这个就是反射方法
		 * 那这个方法名在哪里定义的呢
		 * 抱着这个好奇心搜索这个关键字
         *    readResolveMethod = getInheritableMethod(
         *       cl, "readResolve", null, Object.class);
		 * readObjectMethod赋值成什么呢
		 * 名字赋成readResolve
		 * readResolve他就是我们刚刚写的目标方法名
		 * 就在这里
		 * 终于找到他了
		 * 具体这个源码怎么看呢
		 * 我会debug根据源码里面一行一行看一下
		 * 你们也可以去看源码
		 * 这个是学习框架和源码非常好的一种方式
		 * 那回来
		 * 所以这个方法是通过反射出来的
		 * 也没什么继承关系
		 * 所以只能把这个方法直接写到这里
		 * 一起来debug一下
		 * 看源码的时候也要看关键点
		 * 现在一起debug一下
		 * 我们跟一遍源码
		 * 
		 * 反序列化出来
         * 
         */
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
//
        /**
         * 强转把异常抛出
         * 
         * 这个时候可以调用readObject方法
         * 然后进入readObject0
         * 我们再打一个断点
         * 之后就来到这里了
         * 我们看一下我们刚刚关注的Object这个case
         * case TC_OBJECT:
         * 看到进入到return checkResolve(readOrdinaryObject(unshared));
         * 然后readOrdinaryObject这个方法
         * 下边继续
         * obj = desc.isInstantiable() ? desc.newInstance() : null;
         * 我们看一下desc.isInstantiable()他的返回值是什么
         * 正如我们的预期他的返回值是true
         * 所以他会调一个newInstance
         * 我们单步走一下
         * 这个时候我们在内存里面看一下obj
         * Object obj;
         * 我们看到这个对象其实已经new出来了
         * HungrySingleton
         * 后面我记得还有一个断点F8
         * desc.hasReadResolveMethod()
         * 判断方法有没有
         * 看来进入了if
         * Object rep = desc.invokeReadResolve(obj);
         * 开始invoke这个方法了
         * 进来
         * 注意if (readResolveMethod != null)这个
         * F8过来
         * 判断他是不是空
         * 我们打开看一下
         * 这个Method类型
         * 可以看到name是readResolve
         * 他并不是空
         * 所以F6单步
         * return readResolveMethod.invoke(obj, (Object[]) null);
         * 这个时候反射调用这个方法
         * 会到HungrySingleton类里边
         * private Object readResolve()
         * 这个时候就有了
         * F8这个时候
         * return hungrySingleton;
         * 这个时候就调用了这个方法
         * 而hungrySingleton这个对象我们看一下
         * 他本身已经创建好了
         * 是420
         * 而这个时候
         * return的时候
         * 就把这个对象返回回去了
         * 而不会刚刚通过反射newInstance
         * 这个时候return obj;
         * 返回的是hungrySingleton 420
         * 也就是HungrySingleton这里面的对象
         * F8继续
         * 下边我们就不看了
         * 刚刚讲的这一串呢
         * 是序列号和反序列化的一个重点
         * 核心
         * 现在F8直接过
         * 回到Test里面看console一模一样
         * 可是有一点
         * 我们考虑一下
         * 虽然最终返回的是同一个对象了
         * 但是在这个过程中
         * 其实它实例化对象了
         * 只是最后返回没有返回而已
         * 所以一旦我们的业务场景涉及序列化和反序列化的时候
         * 一定要注意对单例的破坏
         * 这个呢非常重要
         * 希望通过这个能对序列化和反序列化单例的破坏有一个深入的理解
         * 并且学会如何看源码
         * 如何找源码的关键路径
         * 这些技能对成长是非常有益处的
         * 我们接下来看一下对于反射这种情况
         * 我们如何来防御呢
         * 
         * 
         * 
         */
        HungrySingleton newInstance = (HungrySingleton) ois.readObject();
//        EnumInstance newInstance = (EnumInstance) ois.readObject();
//
//        System.out.println(instance.getData());
//        System.out.println(newInstance.getData());
//        System.out.println(instance.getData() == newInstance.getData());

//        Class objectClass = HungrySingleton.class;
//        Class objectClass = StaticInnerClassSingleton.class;

//        Class objectClass = LazySingleton.class;
//        Class objectClass = EnumInstance.class;

//        Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
//
//        constructor.setAccessible(true);
//        EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);


//
//        LazySingleton newInstance = (LazySingleton) constructor.newInstance();
//        LazySingleton instance = LazySingleton.getInstance();



//        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
//        StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();

//        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
//        HungrySingleton instance = HungrySingleton.getInstance();


        /**
         * 在判断之前呢
         * 我们把这个两个对象都输出一下
         * 我们run一下
         * 这里面会抛一个异常
         * 这个异常我们看到就知道了
         * 这里的异常说了
         * 不是可序列化的异常
         * 很简单
         * 我们让他来实现序列号接口
         * 刚刚这个异常是特意留出来的
         * 希望印象深刻
         * instance是001
         * newInstance002
         * 他们是不相等
         * 那目前来看就违背了单例模式的一个初衷
         * 通过序列化和反序列化拿到了不同的对象
         * 而我们只希望拿到同一个对象
         * 那这个事情要怎么解呢
         * 那解这个问题也不难
         * 重要的是理解
         * 理解他的原理是什么
         * 我们这里只是以饿汉式来做例子
         * 其他方式可以自己尝试一下
         * 异曲同工
         * 那么来到这个单例类里面
         * 
         * 
         * 
         */
        System.out.println(instance);
        System.out.println(newInstance);
        
        /**
         * instance和newInstance做一个对比
         * 
         */
        System.out.println(instance == newInstance);

//        EnumInstance instance = EnumInstance.getInstance();
//        instance.printTest();


    }
}
    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();
            }
        }
    }
    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++;
        totalObjectRefs++;
        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);
        }
    }
    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;
        } 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) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                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.
     */
    boolean isInstantiable() {
        requireInitialized();
        return (cons != null);
    }
    /**
     * Returns true if represented class is serializable (but not
     * externalizable) and defines a conformant writeObject method.  Otherwise,
     * returns false.
     */
    boolean hasWriteObjectMethod() {
        requireInitialized();
        return (writeObjectMethod != null);
    }
    /**
     * Invokes the readResolve method of the represented serializable class and
     * returns the result.  Throws UnsupportedOperationException if this class
     * descriptor is not associated with a class, or if the class is
     * non-serializable or does not define readResolve.
     */
    Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException
    {
        requireInitialized();
        if (readResolveMethod != null) {
            try {
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError(th);  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
        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);
                    }
                    domains = getProtectionDomains(cons, cl);
                    writeReplaceMethod = getInheritableMethod(
                        cl, "writeReplace", null, Object.class);
                    readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);
                    return null;
                }
            });
        } else {
            suid = Long.valueOf(0);
            fields = NO_FIELDS;
        }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值