单例模式,一种防反射攻击的写法
介绍
单例模式一般用于只需要一个对象的场景,必须http请求工具类,我们不需要多个,就可以用单例的写法
代码实现
public class UtilSync {
private static UtilSync sync;
private UtilSync(){
System.out.println("util双重锁初始化成功");
}
public static UtilSync getInstance(){
//如果为空才会进去
if(sync==null){// 1
//然后锁定这个工具
synchronized (UtilSync.class){ // 1
//做一下判断,因为有这种可能,就是1步骤判断为true进来了,
// 然后另一个线程也成功判断了,并且都走完步骤,成功new了
//然后我不判断是否为空的话,这里就创建多份util了
if(sync==null){
sync=new UtilSync();
}
}
}
return sync;
}
public void show(){
System.out.println("show----------->");
}
}
于是,我们这种双重锁,防止多线程重复创建对象的代码就写好了,我们测试一下
public class Content {
public static void main(String[] args) {
//这里模拟多线程
CountDownLatch latch=new CountDownLatch(1);
for(int i=0;i<10000;i++){
new Thread(new Runnable() {
@Override
public void run() {
try{
latch.await();
}catch (Exception e){
e.printStackTrace();
}
UtilSync instance1 = UtilSync.getInstance();
}
}).start();
}
latch.countDown();
}
}
output:
util双重锁初始化成功
Process finished with exit code 0
执行结束,防止重复创建对象的功能我们实现了,接下来我们进行反射攻击
于是我们就有了下面的代码
try {
Constructor<UtilSync> declaredConstructor = UtilSync.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
UtilSync utilSync = declaredConstructor.newInstance();
Method show = UtilSync.class.getMethod("show");
show.invoke(utilSync);
} catch (Exception e) {
e.printStackTrace();
}
output:
util双重锁初始化成功
show----------->
Process finished with exit code 0
可以看到,我们的双重锁单例在反射攻击面前不堪一击
我们能不能在代码里加一个像开关一样的东西呢,就是只能实例化对象一次,以后实例化对象都直接报错
于是,带着我们的想法,下面的代码就产生了
public class SingletonNotAttackByReflect
{
private static boolean flag = false;
private static final SingletonNotAttackByReflect INSTANCE = new SingletonNotAttackByReflect();
//保证其不被java反射攻击
private SingletonNotAttackByReflect()
{
synchronized (SingletonNotAttackByReflect.class)
{
if(false == flag)
{
flag = !flag;
System.out.println("初始化完成------->");
}
else
{
throw new RuntimeException("单例模式正在被攻击");
}
}
}
public static SingletonNotAttackByReflect getInstance()
{
return INSTANCE;
}
}
```
我们来测试一下
```shell
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at singleinstance.TestReflectionSingleton.test2(TestReflectionSingleton.java:36)
at singleinstance.TestReflectionSingleton.main(TestReflectionSingleton.java:15)
Caused by: java.lang.RuntimeException: 单例模式正在被攻击
at singleinstance.UtilCantReflection.<init>(UtilCantReflection.java:27)
... 6 more
```
我们看见,反射代码直接报错了,我们再来分析这个能解决反射攻击的构造函数里面的代码
首先定义了一个静态boolean变量,并且附初始值为false
然后在给第二个变量赋值的时候,会走这个工具类的构造方法
这个构造方法会先对这个类进行加锁,防止A线程满足false==flag的条件后,正要对flag进行赋值,然后a线程被挂起,b线程进来条件,然后b线程对flag进行取反,这时a线程也对flag进行取反,然后我们的flag判断就会失效。为了避免这种类似情况,这里直接把这个类作为锁,然后进行判断操作
这个逻辑就相当于做了一个开关,执行第一次构造方法后,下次进来构造方法会直接抛异常,于是很巧妙的解决了反射攻击问题
不过,仅仅这样就可以了吗?我并不觉得,接下来我们看看这种反射攻击方法
这里我们用了一个变量来实现避免反射实例化对象,我们的对象实例化控制在了开关上,那我们就可以对这个开关做点手脚,这里反射修改下开关
```java
try {
Field flag = SingletonNotAttackByReflect.class.getDeclaredField("flag");
flag.setAccessible(true);
flag.set(null, false);
Constructor<SingletonNotAttackByReflect> declaredConstructor = SingletonNotAttackByReflect.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonNotAttackByReflect utilSync = declaredConstructor.newInstance();
System.out.println(utilSync);
System.out.println(UtilCantReflection.GetInstance());
} catch (Exception e) {
e.printStackTrace();
}
```
output:
```shell
初始化完成------->
初始化完成------->
singleinstance.SingletonNotAttackByReflect@74a14482
false
singleinstance.UtilCantReflection@1540e19d
Process finished with exit code 0
```
这种开关控制对象实例化的方法还是有问题的
其实我们根本没必要弄个开关来判断对象能否初始化,我们直接判断实例是否为空就行了
```java
public class SingletonNotAttackByReflect
{
private static final SingletonNotAttackByReflect INSTANCE;
static {
INSTANCE= new SingletonNotAttackByReflect();
}
//保证其不被java反射攻击
private SingletonNotAttackByReflect()
{
synchronized (SingletonNotAttackByReflect.class)
{
if(INSTANCE!=null){
throw new IllegalStateException("已经被实例化了");
}
}
}
public static SingletonNotAttackByReflect getInstance()
{
return INSTANCE;
}
}
```
我们来测试一下
```java
try {
CountDownLatch latch = new CountDownLatch(1);
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
synchronized (SingletonNotAttackByReflect.class) {
// SingletonNotAttackByReflect.getInstance().pringFlag();
Constructor<SingletonNotAttackByReflect> declaredConstructor = SingletonNotAttackByReflect.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonNotAttackByReflect utilSync = declaredConstructor.newInstance();
System.out.println(utilSync);
System.out.println(UtilCantReflection.GetInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
```
otput
```shell
at singleinstance.TestReflectionSingleton$1.run(TestReflectionSingleton.java:58)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: 已经被实例化了
at singleinstance.SingletonNotAttackByReflect.<init>(SingletonNotAttackByReflect.java:16)
... 5 more
java.lang.reflect.InvocationTargetException
at sun.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at singleinstance.TestReflectionSingleton$1.run(TestReflectionSingleton.java:58)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: 已经被实例化了
at singleinstance.SingletonNotAttackByReflect.<init>(SingletonNotAttackByReflect.java:16)
... 5 more
```
我们可以看到,反射失败,因为已经有一个实例了