單例模式,一種防反射攻擊的寫法
介紹
單例模式一般用於只需要一個對象的場景,必須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
```
我們可以看到,反射失敗,因爲已經有一個實例了