单例模式引发相关整理

如何破坏单例模式

示例:

/**
* 如果破坏单例模式
*
* @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)); }
}

结论图

最新文章

  1. 20155206赵飞技能获取经验,C语言学习感想与对JAVA的学习目标
  2. linux进程及进程控制
  3. java 链接jdbc
  4. UI UIView
  5. python3.0_day9_scoket基础之篇
  6. iOS学习之C语言函数
  7. log4j配置文件写法
  8. js清空前后空格
  9. 一个封装HTTP请求的函数(C++)
  10. SE 2014年4月14日
  11. Android网络编程http派/申请服务
  12. CSDN专家吐槽实录
  13. Struts 之 通配符 路径匹配 常量用法 配置默认值
  14. 第4阶段——制作根文件系统之编译配置安装busybox(3)
  15. 【JavaScript动画基础】学习笔记(一)-- 旋转箭头
  16. python07 函数式编程
  17. fiddler对Iphone6s进行抓包教程
  18. 关于Spring MVC跨域
  19. [转]css实现左侧宽度自适应,右侧固定宽度
  20. A-Softmax的总结及与L-Softmax的对比——SphereFace

热门文章

  1. 【DocFX文档翻译】DocFX 入门 (Getting Started with DocFX)
  2. 《http权威指南》读书笔记7
  3. OAuth2简易实战(二)-模拟客户端调用
  4. SpringCache学习实践
  5. GIT的初级使用
  6. JAVA面试题-数组字符串基础
  7. jquery获取radio选中值及遍历
  8. sql server 性能调优之 资源等待之网络I/O
  9. 技术简历这样写,才能得到BAT面试官的青睐
  10. Java-jsoup-解析HTML