欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java设计模式 - 单例模式详解(扩展)

程序员文章站 2022-03-03 09:32:23
单例模式引发相关整理 如何破坏单例模式 示例: /** * 如果破坏单例模式 * * @author sunyang * @date 2018/11/13 20:14 */ public class Singleton7 { private Singleton7(){ System.out.prin ......

单例模式引发相关整理

如何破坏单例模式

示例:

/**
 * 如果破坏单例模式
 *
 * @author sunyang
 * @date 2018/11/13 20:14
 */
public class singleton7 {
    private singleton7(){
        system.out.println("singleton7");
    }
    private static final class singletonholder{
        singletonholder(){
            system.out.println("singletonholder");
        }
        private static final singleton7 instance = new singleton7();
    }
    public static singleton7 getinstance(){
        return singletonholder.instance;
    }
}

测试得出结果

/**
 * @author sunyang
 * @date 2018/11/13 20:18
 */
public class test {

    /**
     * ----------------------start-------------
     *singleton7
     * -----------------------end---------------
     *
     */
    public static void main(string[] args) {
        system.out.println("----------------------start-------------");
        singleton7.getinstance();
        system.out.println("-----------------------end---------------");
    }
}

上图的单例,最主要的一步是将构造方法私有化,从而外界无法new对象。但是java的反射可以强制访问private修饰的变量,方法和构造函数,如图

/**
 * @author sunyang
 * @date 2018/11/13 20:31
 */
public class reflecttest {

    /**
     * singleton7
     * singleton7
     * 反射获取构造器和直接获取构造器,比较是否同一个:false
     */

    public static void main(string[] args) throws nosuchmethodexception, illegalaccessexception, invocationtargetexception, instantiationexception {
            class<singleton7> singleton7class = singleton7.class;
        constructor<singleton7> constructor = singleton7class.getdeclaredconstructor();
        constructor.setaccessible(true);
        //反射获取构造函数
        singleton7 singleton1 = constructor.newinstance();

        singleton7 singleton2 = singleton7.getinstance();

        system.out.println("反射获取构造器和直接获取构造器,比较是否同一个:" + (singleton1 == singleton2));

    }
}
联想

java中的四种创建对象的方式

type explan
new 需要调用构造函数
reflect 需要调用构造函数,免疫一切访问权限的限制public,private等
clone 需要实现cloneable接口,分浅复制,深层
serializable 1.将对象保存在硬盘中 2.通过网络传输对象,需要实现serializable

单例模式,是不能抵抗反射,clone,序列化的破坏的。

如何保护单例模式

思路:

对于clone和序列化,可以在设计的过程中不直接或间接的去实现cloneable和serializable接口即可。

对于反射,可以通过在调用第二次构造函数的方式进行避免。

尝试解决代码:

/**
 * 保护单例模式测试demo
 *
 * @author sunyang
 * @date 2018/11/14 10:32
 */
public class reflecttestprotect {
    public static void main(string[] args) {
        try {
            class<singleton7protect> clazz = singleton7protect.class;
            constructor<singleton7protect> constructor = clazz.getdeclaredconstructor(null);
            constructor.setaccessible(true);
            singleton7protect singleton1 = constructor.newinstance();
            singleton7protect singleton2 = singleton7protect.getinstance();
            system.out.println("保护单例模式:"+ (singleton1 == singleton2));
        }catch (exception e){
            e.printstacktrace();
        }
    }
}

结果图

Java设计模式 - 单例模式详解(扩展)

 

推荐新写法

jdk1.5以后,实现单例新写法,只需要编写一个包含单个元素的枚举类型。

分析

枚举类能够实现接口,但不能继承类,枚举类使用enum定义后再编译时就默认继承了java.lang.enum类,而不是普通的继承object类。

枚举类会默认实现serializable和comparable两个接口,且采用enum声明后,该类会被编译器加上final声明,故该类是无法继承的。枚举类的内部定义的枚举值就是该类的实例。除此之外,枚举类和普通类一致。因此可以利用枚举类来实现一个单例模式。直观图如下:

 

Java设计模式 - 单例模式详解(扩展)

 

推荐写法代码:

/**
 * 新写法:枚举类来实现一个单例,
 * 来测试破坏,是否会成功?
 *
 * @author sunyang
 * @date 2018/11/14 11:22
 */
public enum  singleton8 {
    instance;

    public static singleton8 getinstance(){
        return instance;
    }
}

测试是否反射破坏代码:

/**
 * 枚举测试反射是否能破坏单例
 * 结论:可以防止单例模式被侵犯
 * @author sunyang
 * @date 2018/11/14 11:26
 */
public class enumtest {
    public static void main(string[] args) throws nosuchmethodexception, illegalaccessexception, invocationtargetexception, instantiationexception {
        class<singleton8> clazz = singleton8.class;
        constructor<singleton8> constructor = clazz.getdeclaredconstructor();
        constructor.setaccessible(true);
        constructor.newinstance();
    }
}

结果图:

 

Java设计模式 - 单例模式详解(扩展)

 

结论:单元素的枚举类型已经成为实现单例模式的最佳方法。

源码分析

java.lang.reflect.constructor#newinstance

@callersensitive
    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);
            }
        }
        //反射再通过newinstance创建对象时,会检查该类是否enum修饰,如果是则抛出异常,反射失败。
        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;
    }

扩展:测试对枚举类进行序列化操作代码:

/**
 * 测试枚举类是否能防止序列化破坏单例模式
 * 结果:可以防止序列化破坏单例模式
 *
 * @author sunyang
 * @date 2018/11/14 11:52
 */
public class serializabletest {

    public static void main(string[] args) throws exception{
        file objectfile = new file("singleton8.javaobject");
        singleton8 instance1 = singleton8.instance;
        singleton8 instance2 = null;
        objectoutputstream oos = null;
        objectinputstream ois = null;
        try {
            //序列化到本地
            oos = new objectoutputstream(new fileoutputstream(objectfile));
            oos.writeobject(instance1);
            oos.flush();
            //反序列化到内存
            ois = new objectinputstream(new fileinputstream(objectfile));
            instance2= (singleton8) ois.readobject();
        }catch (exception e){

        }finally {
            oos.close();
            ois.close();
        }

        //true,说明两者引用着同一个对象
        system.out.println(objects.equals(instance1, instance2));

    }
}

结论图

 

Java设计模式 - 单例模式详解(扩展)