通常我们所使用的单例模式,我们都可以使用反射使它不再单例,如下饿汉式的单例模式:
测试案例如下:
其中singleton1、singleton2都是通过我们所实现的单例模式来获取的对象,他们应该是同一个对象,singleton3则是通过反射获取无参构造器,constructor.setAccessible(true)来获取访问权限,最后通过无参构造器来创建一个对象singleton3,singleton3和上述两者应该不是同一个对象,测试结果如下:
所以说通常我们所使用的单例模式,我们都可以使用反射使它不再单例。然而单例使用枚举的话,却可以避免被反射。
单例如下:
反射如下:
然后就报错:
没有这个无参构造器,通过调试Singleton.class.getDeclaredConstructors()获取所有构造器,会发现并没有我们所设置的无参构造器,只有一个参数为(String.class,int.class)构造器,然后我们就可以明白了,这里的参数其实就是枚举的名字和所在枚举中位置,即枚举的name和ordinal两个属性,枚举的源码如下:
枚举Enum是一个抽象类,一个类一旦声明为枚举,其实就是继承了Enum,所以会有(String.class,int.class)的构造器(就是父类Enum的构造器),具体的原理可以根据生成的字节码反编译后得知,可以参考这篇文章http://pf-miles.iteye.com/blog/187155#bc2340028。
既然是可以获取到父类Enum的构造器,那我们就使用该构造器看能不能创建出对象:
然后也报错:
之前的错是说没有构造器,这次我们能够拿到构造器了,只是在使用构造器执行newInstance("otherInstance",9)方法时抛出异常,说不能够反射枚举,具体源码如下:
也就是说反射在通过newInstance创建对象时,会检查该类是否是枚举类,如果是,则抛出异常,反射失败。
也就是说使用枚举可以避免被反射,从而可以达到单例的效果。
1
2
3
4
5
6
7
8
9
10
|
public
final
class
Singleton {
private
static
final
Singleton instance=
new
Singleton();
private
Singleton(){}
public
static
Singleton getInstance(){
return
instance;
}
}
|
测试案例如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
Singleton singleton1=Singleton.getInstance();
Singleton singleton2=Singleton.getInstance();
Constructor<Singleton> constructor=Singleton.
class
.getDeclaredConstructor();
constructor.setAccessible(
true
);
Singleton singleton3=constructor.newInstance();
System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton3);
System.out.println(singleton1==singleton2);
System.out.println(singleton1==singleton3);
|
其中singleton1、singleton2都是通过我们所实现的单例模式来获取的对象,他们应该是同一个对象,singleton3则是通过反射获取无参构造器,constructor.setAccessible(true)来获取访问权限,最后通过无参构造器来创建一个对象singleton3,singleton3和上述两者应该不是同一个对象,测试结果如下:
1
2
3
4
5
|
com.lg.design.singleton.hungry.Singleton
@15e3d24a
com.lg.design.singleton.hungry.Singleton
@15e3d24a
com.lg.design.singleton.hungry.Singleton
@20030380
true
false
|
所以说通常我们所使用的单例模式,我们都可以使用反射使它不再单例。然而单例使用枚举的话,却可以避免被反射。
单例如下:
1
2
3
4
5
6
|
public
enum
Singleton {
instance;
private
Singleton(){}
}
|
反射如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
Singleton singleton1=Singleton.instance;
Singleton singleton2=Singleton.instance;
Constructor<Singleton> constructor=Singleton.
class
.getDeclaredConstructor();
constructor.setAccessible(
true
);
Singleton singleton3=constructor.newInstance();
System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton3);
System.out.println(singleton1==singleton2);
System.out.println(singleton1==singleton3);
|
然后就报错:
1
2
3
4
|
Exception in thread
"main"
java.lang.NoSuchMethodException: com.lg.design.singleton.enumsingleton.Singleton.<init>()
at java.lang.Class.getConstructor0(Class.java:
2849
)
at java.lang.Class.getDeclaredConstructor(Class.java:
2053
)
at com.lg.design.singleton.enumsingleton.Test.main(Test.java:
14
)
|
没有这个无参构造器,通过调试Singleton.class.getDeclaredConstructors()获取所有构造器,会发现并没有我们所设置的无参构造器,只有一个参数为(String.class,int.class)构造器,然后我们就可以明白了,这里的参数其实就是枚举的名字和所在枚举中位置,即枚举的name和ordinal两个属性,枚举的源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
abstract
class
Enum<E
extends
Enum<E>>
implements
Comparable<E>, Serializable {
private
final
String name;
public
final
String name() {
return
name;
}
private
final
int
ordinal;
public
final
int
ordinal() {
return
ordinal;
}
protected
Enum(String name,
int
ordinal) {
this
.name = name;
this
.ordinal = ordinal;
}
public
String toString() {
return
name;
}
//略
}
|
枚举Enum是一个抽象类,一个类一旦声明为枚举,其实就是继承了Enum,所以会有(String.class,int.class)的构造器(就是父类Enum的构造器),具体的原理可以根据生成的字节码反编译后得知,可以参考这篇文章http://pf-miles.iteye.com/blog/187155#bc2340028。
既然是可以获取到父类Enum的构造器,那我们就使用该构造器看能不能创建出对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Singleton singleton1=Singleton.instance;
Singleton singleton2=Singleton.instance;
Constructor<Singleton> constructor=Singleton.
class
.getDeclaredConstructor(String.
class
,
int
.
class
);
//Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();
constructor.setAccessible(
true
);
Singleton singleton3=constructor.newInstance(
"otherInstance"
,
9
);
//Singleton singleton3=constructor.newInstance();
System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton3);
System.out.println(singleton1==singleton2);
System.out.println(singleton1==singleton3);
|
然后也报错:
1
2
3
|
Exception in thread
"main"
java.lang.IllegalArgumentException: Cannot reflectively create
enum
objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:
521
)
at com.lg.design.singleton.enumsingleton.Test.main(Test.java:
16
)
|
之前的错是说没有构造器,这次我们能够拿到构造器了,只是在使用构造器执行newInstance("otherInstance",9)方法时抛出异常,说不能够反射枚举,具体源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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);
}
}
//我们关注的重点,如果类含有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();
}
return
(T) ca.newInstance(initargs);
}
|
也就是说反射在通过newInstance创建对象时,会检查该类是否是枚举类,如果是,则抛出异常,反射失败。
也就是说使用枚举可以避免被反射,从而可以达到单例的效果。