第三条:用私有构造器或者枚举类型强化Singleton属性
一、Singleton属性
public class Student {
public String name;
public int age;
private static final Student instance=new Student();
private Student()
{
name="sean";
age=23;
}
public static Student getInstance()
{
return instance;
}
}
正常情况下Student类只能有一个实例对象,无论调用多少次getInstance()方法返回的都是同一个对象。很明显该Singleton类用反射破解可以生成第二个对象,关于反射破解的方法在另一篇文章中点击此连接查看反射破解方法。
二、防止反射破解Singleton类的方法
1.第一种办法是修改构造器法
public class Student {
public String name;
public int age;
private static boolean flag=false;
private static final Student instance=new Student();
private Student()
{
if(flag==false)
{
name="sean";
age=23;
flag=true;
}
else
{
throw new RuntimeException("The singleton is under attacking!");
}
}
public static Student getInstance()
{
return instance;
}
}
让程序检测到非第一次调用构造器时抛出异常阻止反射破解的办法构建第二个对象实例,编写一个测试程序如下:
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
Student a=Student.getInstance();
System.out.println("name:"+a.name+" "+"age:"+a.age);
Class stuClass=Class.forName("person.Student");
Constructor con=stuClass.getDeclaredConstructor(null);
con.setAccessible(true);
Student b=(Student) con.newInstance(); //利用破解的构造器建立第二个对象实例
System.out.println("name:"+b.name+" "+"age:"+b.age);
}
}
程序运行结果如下:
name:sean age:23
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at test.Test.main(Test.java:20)
Caused by: java.lang.RuntimeException: The singleton is under attacking!
at person.Student.<init>(Student.java:20)
... 5 more
可以看到第一个且唯一一个实例对象创建成功,当利用反射破解的构造器构建第二个对象实例时程序报错阻止了破坏Student类的Singleton属性。
2.第二种办法是枚举法
public enum Student {
INSTANCE; //枚举中唯一的实例构成单例模式
public String name;
public int age;
public void SetName(String name)
{
this.name=name;
}
public void SetAge(int age)
{
this.age=age;
}
}
再用如下反射破解代码尝试创建第二个实例对象:
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
Student a=Student.INSTANCE;
a.SetName("Sean");
a.SetAge(23);
System.out.println("name:"+a.name+" "+"age:"+a.age);
Class stuClass=Class.forName("person.Student");
Constructor con=stuClass.getDeclaredConstructor(null);
con.setAccessible(true);
Student b=(Student) con.newInstance(); //利用破解的构造器建立第二个对象实例
b.SetName("moon");
b.SetAge(31);
System.out.println("name:"+b.name+" "+"age:"+b.age);
}
}
运行结果如下:
name:Sean age:23
Exception in thread "main" java.lang.NoSuchMethodException: person.Student.<init>()
at java.lang.Class.getConstructor0(Unknown Source)
at java.lang.Class.getDeclaredConstructor(Unknown Source)
at test.Test.main(Test.java:20)
可以看到第一个实例对象创建成功,成功输出姓名和年龄,但是后面的反射破解代码就报错了,成功阻止了创建第二个实例对象。至于枚举法防止反射破解的原理简单说来就是因为枚举类型的特殊性,枚举本质上是一个特殊的类,因为枚举的功能需要,枚举以外的代码自然不能在枚举中创建新的实例,不然枚举也就没有意义了,为了实现该功能需求,枚举类的构造器是由编译器管理的,编程人员不能调用,所以这就防止了调用构造器的反射破解。