单例设计模式——枚举类单例源码分析

单例设计模式——枚举类单例源码分析

情景

/**
 * 枚举
 * 实现简单,JVM从根本上提供保障,避免反射和反序列化的漏洞
 * 无法延迟加载
 * @author Administrator
 */
public enum  SingletonDemo05 {
    /**
     * 枚举类对象
     */
    INSTANCE;
    private Object data;
    public Object getData(){
        return data;
    }
    public void setData(Object data){
        this.data=data;
    }
    public static SingletonDemo05 getInstance(){
        return INSTANCE;
    }
}

尝试序列化破坏枚举单例

        SingletonDemo05 o3=SingletonDemo05.getInstance();
        FileOutputStream fos=new FileOutputStream("D:\\Object.txt");
        ObjectOutputStream oos=new ObjectOutputStream(fos);
        oos.writeObject(o3);
        oos.close();
        fos.close();
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("D:\\Object.txt"));
        Object o4=ois.readObject();
        System.out.println(o3==o4);

序列化不会破坏枚举类的单例

image-20200514155539159

尝试反射破坏枚举单例

 		Constructor c1 = SingletonDemo05.class.getDeclaredConstructor();
        c1.setAccessible(true);
        Object obj1=c1.newInstance();
        Constructor c2 = SingletonDemo05.class.getDeclaredConstructor();
        c2.setAccessible(true);
        Object obj2=c1.newInstance();
        System.out.println("创建的两个对象是否相同:"+(obj1==obj2));

枚举类单例禁止反射实例化

image-20200514155859320

源码分析

序列化:

首先我们使用反编译工具jad反编译SingletonDemo05.class文件,生成SingletonDemo05.jad文件。可以看到代码中有一段:

 static 
    {
        INSTANCE = new SingletonDemo05("INSTANCE", 0);
        $VALUES = (new SingletonDemo05[] {
            INSTANCE
        });
    }

即枚举单例模式中,在静态代码块中给INSTANCE赋值,类似于饿汉式。即枚举类加载时,INSTANCE对象就实例化加载进内存了。那为什么序列化不会破坏单例呢。我们回到ObjectInputStream类的readObject0()方法中,查看枚举类的相关方法

/**
     * Underlying readObject implementation.
     */
    private Object readObject0(boolean unshared) throws IOException {
    	···
           case TC_ENUM:
                    return checkResolve(readEnum(unshared));  
        ···
    }

进入readEnum(boolean unshared)方法

/**
     * Reads in and returns enum constant, or null if enum type is
     * unresolvable.  Sets passHandle to enum constant's assigned handle.
     */
    private Enum<?> readEnum(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }

        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }

        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }

        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }

枚举类的赋值

Enum<?> en = Enum.valueOf((Class)cl, name);
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

枚举类型通过类名和类对象类只能找到唯一的一个枚举对象,因为枚举类在类加载式实例化了INSTANCE枚举类,那么在内存中根据类名和类对象类也就只能找到这一个INSTANCE枚举类对象,也就保证了枚举类的单例。

反射:

关于反射我们直接查看报错和Enum源码

image-20200514155859320

报错行

Constructor c1 = SingletonDemo05.class.getDeclaredConstructor();

即没有找到无参的构造方法,接着我们查看Enum源码

protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

只有一个protected修饰的有参构造方法。我们根据他的有参方法修改测试类

		Constructor c1 = SingletonDemo05.class.getDeclaredConstructor(String.class,int.class);
        c1.setAccessible(true);
        Object obj1=c1.newInstance("xxbb",1);

运行一下

image-20200514171140108

报错行

 Object obj1=c1.newInstance("xxbb",1);

这里就很直接告诉你不能通过反射实例化枚举对象,我们查看下newInstance()方法的源码

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

可以看到在方法中直接进行了判断,如果修饰符是Modifier.ENUM就抛出异常,也就是说在jdk1.8中不允许通过反射实例化枚举类对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值