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(); } } }
结果图
推荐新写法
jdk1.5
以后,实现单例新写法,只需要编写一个包含单个元素的枚举类型。
分析
枚举类能够实现接口,但不能继承类,枚举类使用enum定义后再编译时就默认继承了java.lang.enum类,而不是普通的继承object类。
枚举类会默认实现serializable和comparable两个接口,且采用enum声明后,该类会被编译器加上final声明,故该类是无法继承的。枚举类的内部定义的枚举值就是该类的实例。除此之外,枚举类和普通类一致。因此可以利用枚举类来实现一个单例模式。直观图如下:
推荐写法代码:
/** * 新写法:枚举类来实现一个单例, * 来测试破坏,是否会成功? * * @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.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)); } }
结论图