package com.woniuxy.sd.service; public class Test { private static Test t = new Test(); private Test(){ } public static Test getInstance(){if(null == t)t = new Test(); return t;}}
上面是我们常见的一种单例模式写法,单例模式为保证对象的唯一性,私有化构造函数为保证其除自己之外不能被实例化,static的实例在压栈时将压往方法区静态空间,再由于静态空间唯一性来保证实例唯一,最后由一个公有方法返回该对象实例,那么在上述代码中只需要调用公有返回方法则可得到该实例,同时也确保了实例唯一。
Test.getInstance();
对于这样的方法,无论调用多少次得到的结果都是一样:
System.out.println(Test.getInstance()); System.out.println(Test.getInstance());
执行后得到:
com.woniuxy.sd.service.Test@9cab16 com.woniuxy.sd.service.Test@9cab16
那么,只要构造函数不能被调用,看来实例的确是唯一的,而构造函数本身为private的,的确不能被调用,看来这个单例模式是没有问题的。
但实际上这个单例模式有很大的问题,它并不能保证对象的唯一性,用户可以直接通过AccessibleObject.setAccessible方法来调问其私有构造函数。
javaAPI中对java.lang.reflect.AccessibleObject有如下描述
AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。在反射对象中设置 accessible 标志允许具有足够特权的复杂应用程序(比如 Java Object Serialization 或其他持久性机制)以某种通常禁止使用的方式来操作对象。
而对其setAccessible方法有两次重载,描述如下
static void |
|
void |
|
对第一种方法,要求给出一个AccessibleObject数组,和一个布尔值,当布尔值为true的时候表示当前不检查访问修饰符,而第二种方法是一个非静态的方法,在看AccessibleObject的构造函数
构造方法摘要 | |
---|---|
protected |
|
那么很显然,想直接通过new AccessibleObject的方法得到AccessibleObject是不可行的,可以选择通过静态方法或者子类得到实例,但很遗憾的是该类没有返回该类实例的静态方法,对于AccessibleObject来说他拥有如下子类。
直接已知子类:Constructor, Field, Method
Constructor这个子类在API中描述如下
Constructor 提供关于类的单个构造方法的信息以及对它的访问权限。
Constructor
允许在将实参与带有基础构造方法的形参的 newInstance() 匹配时进行扩展转换,但是如果发生收缩转换,则抛出IllegalArgumentException
同样该类没有返回实例的方法,没有可用的构造函数,但该类可以通过Class.getConstructor和Class.getDeclaredConstructors()
来返回实例。
Constructor<T> |
|
Constructor<T> |
|
Constructor[] |
|
可变参数Class... parameterTypes表示构造函数的参数,第一种方法将返回公共构造函数,而第二种和第三种将返回全部构造函数
因此通过上述描述,可以完成调用私有构造函数
Class clzz = Class.forName("com.woniuxy.sd.service.Test"); Constructor[] c = clzz.getDeclaredConstructors(); AccessibleObject.setAccessible(c,true); System.out.println(c[0].newInstance()); System.out.println(c[0].newInstance());
完成上述代码执行后你会发现原本的单例不在单例,它不在保证实例唯一
com.woniuxy.sd.service.Test@9cab16 com.woniuxy.sd.service.Test@1a46e30
为了解决这个问题,因此我们可以考虑在Test的构造函数上加上一个判断,当第二次实例则抛出一个自定义异常,为什么在构造函数上加判断?因为无论通过反射还是直接new都离不开对构造函数的调用。
private Test() throws InstanceException {
i++;
System.out.println(i);
if(i >= 2)
throw new InstanceException();
}
蜗牛学院,只为成就更好的你!这样创新的模式,值得你的选择!
还在等什么,赶快关注蜗牛学院官方微信,加入到蜗牛学院的大家庭中来吧!