如何防止反射破坏单例?
参考:https://www.jianshu.com/p/5f529f18821a
基于单例模式,有很多实现方法,如:饿汉,懒汉。但是大多只会考虑到单实例和线程安全的问题,很少会考虑到反射对其的破坏。
由于反射这种Bug一样的存在,可以在系统任意地方实例化出一个对象。
//饿汉单例模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
//通过反射破坏单例模式
public class Test {
public static void main(String[] args) throws Exception{
Singleton s1 = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton s2 = constructor.newInstance();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
反射可以直接调用构造函数,哪怕是私有的。
唯一的方法就是:
1.保证构造函数Singleton()只能别调用一次,这个才是关键。
2.利用好静态变量,因为静态变量有且只有一个,这个是JVM保证的。
public class Singleton {
private static int count = 0;
private static Singleton instance = null;
private Singleton(){
synchronized (Singleton.class) {
if(count > 0){
throw new RuntimeException("创建了两个实例");
}
count++;
}
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) throws Exception {
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton s1 = constructor.newInstance();
Singleton s2 = constructor.newInstance();
}
}
1.同步语句块synchronized()保证线程安全的生成实例
2.静态变量count保证唯一可靠,用来判断实例是否已经新增
扩展: 通过Java的序列化破坏单例
大概的思路就是:把实例对象写进任意文件中,然后读取出来构造新的对象
参考:https://www.jianshu.com/p/56c9bbcf0832
@Test
public void test() throws Exception{
SingletonDemo6 nomarlInstance1 = SingletonDemo6.getInstance();
//把对象写入文件
File file = new File("/xxx/xxx/xxx/xxx/xxx/SingletonDemo/a.txt");
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(nomarlInstance1);
oos.close();
fos.close();
//序列化把对象读取
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
SingletonDemo6 serilizeInstance = (SingletonDemo6) ois.readObject();
System.out.println(nomarlInstance1);
System.out.println(serilizeInstance);
}
序列化破坏就三个步骤:
1.获得单例对象
2.对象写进文件
3.文件流反序列化为对象(实际上利用了字节码Class)
1我们阻止不了,2我们也阻止不了,只能寄希望于3。
//JVM中反序列化得语句
ois.readObject();
//源码,转诶object的时候调用readOrdinaryObject方法
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
//readOrdinaryObject方法里面直接调用了invokeReadResolve,看意思应该是要调用某个方法
Object rep = desc.invokeReadResolve(obj);
//里面真的调用了一个方法readResolveMethod,readResolveMethod是成员变量
return readResolveMethod.invoke(obj, (Object[]) null);
//点进去看看,这个成员变量指向了对象的readResolve方法
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
so,应该是反序列化的时候调用了对象的readResolve做到的。
为了防止被反序列化,那我们就自己重写readResolve方法,让反序列化返回原本的单例对象!!!
private Object readResolve() throws ObjectStreamException{
return SingletonDemo6.s1;
}
当然上述操作,具体怎么做还是的看你是饿汉还是懒汉。
目前来讲,从单例可靠性来说,比较好的方法是:
饿汉:
1.直接使用饿汉式+重写readResolve
2.直接使用枚举(自带线程安全和无法序列化新的枚举)
懒汉:
1.匿名内部类+重写readResolve
如何防止反射调用私有方法?
大家一致会以为,private是私有不允许访问的,但是在反射看来都不是事。
那有没有方法防止呢?
没有!
java中有个配置叫SecurityManager,通过对jvm的配置来启用安全策略,然后在反射时进行权限检查。
上述的话未经过验证,但是哪怕是有设置,依然可以通过启动java的参数去做更改,如:java -jar xxx.jar -Dspring.server.port:8888。只要是参数,都没有命令行的级别高,因此只要有人有权限,根本没办法从根本上防止反射调用私有方法。
总结
说了那么多防止!防止!防止!
其实为什么要防止,安全性?
同为开发,真要破坏根本防不了,还需要那么曲折吗?
实际上的安全性,指的是对外,比如hash攻击,密钥被破解这些。
反射的创建不是让你去各种各样的防止这个那个。
更多的是提供了一种可能:
1.提供一种可能: 让你封装类型动态的方法;
2.提供一种可能: 对原有设计错误的补救;
网友评论