单例模式-Enum枚举单例

2019-11-24  本文已影响0人  wbpailxt

用枚举来实现单例
枚举类在多线程情况下也是线程安全的,具体原因下文反编译的时候会提及。

package com.geely.design.pattern.creational.singleton;

/**
 * Created by geely
 */
public enum EnumInstance {
    INSTANCE{
        @Override
        protected void printTest(){
            System.out.println("INSTANCE print test");
        }
    };
    protected void printTest(){
        System.out.println("EnumInstance print test");
    }
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
    public static EnumInstance getInstance(){
        return INSTANCE;
    }
}

枚举类天然的可序列化机制,能够保证不会出现多次实例化的情况。
先测试这个instance枚举是否会被序列化破坏单例模式。

package com.geely.design.pattern.creational.singleton;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by geely
 */
public class Test {
    public static void main(String[] args) throws IOException, NoSuchMethodException{
        EnumInstance instance = EnumInstance.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));
        EnumInstance newInstance = (EnumInstance) ois.readObject();

        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);
    }
}

结果:


图片.png

没有被破坏单例模式。

一般用的是枚举类持有的对象data,再测试一下instance持有的对象有没被破坏单例模式。

package com.geely.design.pattern.creational.singleton;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by geely
 */
public class Test {
    public static void main(String[] args) throws IOException, NoSuchMethodException{
        EnumInstance instance = EnumInstance.getInstance();
        instance.setData(new Object());
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);

        File file = new File("singleton_file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        EnumInstance newInstance = (EnumInstance) ois.readObject();

        System.out.println(instance.getData());
        System.out.println(newInstance.getData());
        System.out.println(instance.getData() == newInstance.getData());

    }
}

结果:


图片.png

枚举持有的对象也是没有被序列化破坏单例模式。

来看看序列化和反序列化对枚举类是怎么处理的。


图片.png

通过readString(false)方法获取到枚举对象的名称name,通过EnumInstance类型和name来获得枚举常量,那因为枚举中的name是唯一的,并且对应一个枚举常量,所以拿到的肯定是唯一的常量对象,这样就没有创建新的对象,维持了这个对象的单例属性。

反射攻击

package com.geely.design.pattern.creational.singleton;

import java.lang.reflect.Constructor;

/**
 * Created by geely
 */
public class Test {
    public static void main(String[] args) throws NoSuchMethodException {
        Class objectClass = EnumInstance.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);

    }
}

结果


图片.png

提示没有无参构造函数


图片.png
看java.lang.Enum类源码确实如此。
那我们改造一下测试代码。
package com.geely.design.pattern.creational.singleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by geely
 */
public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objectClass = EnumInstance.class;
        Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        EnumInstance instance = (EnumInstance) constructor.newInstance("wbp",666);
    }
}

结果:


图片.png

虽然获取到了对应的构造函数,但是提示说不能够用反射新建Enum对象。
这是为什么呢?我们看一下Constructor源码


图片.png
这里判断使用newInstance()方法的目标类是不是枚举类型,是就会抛出错误。

序列化相关的和反射相关的都是由别的类来处理的,也就是ObjectInputSteam和Constructo这两个类来处理枚举类序列化和反射相关的逻辑,所以枚举类是天然地可以抵御序列化对单例的破坏和反射攻击的。

枚举类在多线程情况下为什么是线程安全的?
对EnumInstance进行反编译


图片.png
上一篇下一篇

猜你喜欢

热点阅读