推薦:Java設計模式彙總
反射破壞單例模式以及如何防禦
需要了解實作單例模式的各種方法,可以參考下方這篇部落格。
設計模式-單例模式(Singleton Pattern)
單例模式類Singleton,是使用靜态内部類實作的單例模式。
package com.kaven.design.pattern.creational.singleton;
public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return Inner.instance;
}
private static class Inner{
private static final Singleton instance = new Singleton();
}
}
接下來,使用反射來破壞單例模式。
package com.kaven.design.pattern.creational.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectionDestroyTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = Singleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance = Singleton.getInstance();
Singleton newInstance = (Singleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
因為在單例模式中的構造器是私有的,在Singleton類外部是不能随便調用的,但是通過反射就不一定了,通過這段代碼
constructor.setAccessible(true)
便可以得到構造器的通路權。
結果:
com.kaven.design.pattern.creational.singleton.Singleton@4554617c
com.kaven.design.pattern.creational.singleton.Singleton@74a14482
false
很明顯,反射确實破壞了單例模式。
我們如何應對呢?
即便是通過反射來建立執行個體,也是調用類中的構造器來實作的,是以我們可以在構造器中做文章。
改造
Singleton類
中的私有構造器如下:
package com.kaven.design.pattern.creational.singleton;
public class Singleton{
private Singleton(){
if(Inner.instance != null){
throw new RuntimeException("單例模式禁止反射建立執行個體!");
}
}
public static Singleton getInstance(){
return Inner.instance;
}
private static class Inner{
private static final Singleton instance = new Singleton();
}
}
結果:
Exception in thread "main" 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 com.kaven.design.pattern.creational.singleton.ReflectionDestroyTest.main(ReflectionDestroyTest.java:12)
Caused by: java.lang.RuntimeException: 單例模式禁止反射建立執行個體!
at com.kaven.design.pattern.creational.singleton.Singleton.<init>(Singleton.java:6)
... 5 more
很顯然報異常了,這樣便防止了這種方法實作的單例模式被反射破壞。
餓漢式
實作的單例模式都可以這樣來防止單例模式被反射破壞。
懶漢式實作的單例模式是不可以防止被反射破壞的。
用
雙重檢查鎖式
實作的單例模式來進行測試,其他的
懶漢式
實作的單例模式同理。
package com.kaven.design.pattern.creational.singleton;
public class Singleton {
private static volatile Singleton instance ;
private Singleton(){
if(instance != null){
throw new RuntimeException("單例模式禁止反射建立執行個體!");
}
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
上面的測試方法是沒有問題的。
我們改一改測試方法:
package com.kaven.design.pattern.creational.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectionDestroyTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = Singleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton newInstance = (Singleton) constructor.newInstance();
Singleton instance = Singleton.getInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
很明顯,我們把通過
反射建立執行個體
和調用
靜态方法getInstance()
獲得執行個體的位置互換了,是以一開始通過
反射建立執行個體
調用構造器,此時構造器中的判斷
instance != null
是無用的,是以這種方法是不适用
懶漢式
實作的單例模式來防止被反射破壞的。
結果也很明顯:
com.kaven.design.pattern.creational.singleton.Singleton@4554617c
com.kaven.design.pattern.creational.singleton.Singleton@74a14482
false
我們可以使用信号量嗎?
代碼如下:
package com.kaven.design.pattern.creational.singleton;
public class Singleton {
private static volatile Singleton instance ;
private static boolean flag = true;
private Singleton(){
if(flag){
flag = false;
}
else{
throw new RuntimeException("單例模式禁止反射建立執行個體!");
}
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
這樣我們是不是隻能調用一次構造器了,是以也就可以防止反射了?
反射可以獲得私有構造器的通路權,難道就不能獲得私有靜态變量的通路權嗎?
很顯然是可以的,是以這種方法也是行不通的。
如果定義信号量為
final
呢?就更不行了吧,因為都不能更改。
測試:
package com.kaven.design.pattern.creational.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class ReflectionDestroyTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class objectClass = Singleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance = Singleton.getInstance();
Field flag = objectClass.getDeclaredField("flag");
flag.setAccessible(true);
flag.set(instance , true);
Singleton newInstance = (Singleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
結果:
com.kaven.design.pattern.creational.singleton.Singleton@1540e19d
com.kaven.design.pattern.creational.singleton.Singleton@677327b6
false
很顯然出問題了。
是以
懶漢式實作的單例模式是不可以防止被反射破壞的。
評論中的例子
例一:
private Singleton() {
// 防止反射對單例的破壞
if(instance!=null){ // 單例已有執行個體
throw new RuntimeException("禁止使用反射建立單例");
}
instance = this;
}
測試:
package com.kaven.system;
import lombok.SneakyThrows;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Singleton {
private static volatile Singleton instance ;
private Singleton() throws InterruptedException {
// 防止反射對單例的破壞
if(instance!=null){ // 單例已有執行個體
throw new RuntimeException("禁止使用反射建立單例");
}
// 如果執行到這裡,需要等待一段時間才能繼續執行,比如時間片用完了
Thread.sleep(100);
instance = this;
System.out.println(this + " " + Thread.currentThread().getName());
}
public static Singleton getInstance() throws InterruptedException {
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException, InterruptedException {
Class objectClass = Singleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
Thread thread = new Thread(new Singleton.myRunnable());
thread.setName("kaven");
thread.start();
constructor.newInstance();
}
static class myRunnable implements Runnable{
@SneakyThrows
@Override
public void run() {
Singleton.getInstance();
}
}
}
結果:
還是可以破壞單例模式的,因為我們不知道時間片什麼時候用完,或者在執行過程中遇到什麼問題。
例二:
private Singleton(){
synchronized (Singleton.class) {
// 防止反射對單例的破壞
if (instance != null) { // 單例已有執行個體
throw new RuntimeException("禁止使用反射建立單例");
}
instance = this;
}
}
測試:
package com.kaven.system;
import lombok.SneakyThrows;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Singleton {
private static volatile Singleton instance ;
private static boolean throwException = true;
private Singleton(){
synchronized (Singleton.class) {
// 防止反射對單例的破壞
if (instance != null) { // 單例已有執行個體
throw new RuntimeException("禁止使用反射建立單例");
}
System.out.println(this + " " + Thread.currentThread().getName());
// 如果出現異常,異常結束時會釋放掉鎖
if(Singleton.throwException) throw new RuntimeException();
instance = this;
}
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
Class objectClass = Singleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
Thread thread = new Thread(new Singleton.myRunnable());
thread.setName("kaven");
thread.start();
Singleton singleton = null;
try {
singleton = (Singleton) constructor.newInstance();
} catch (Exception e){
e.printStackTrace();
} finally {
// 雖然還是null
System.out.println(singleton);
}
}
static class myRunnable implements Runnable{
@SneakyThrows
@Override
public void run() {
Thread.sleep(50);
Singleton.throwException = false;
Singleton singleton = Singleton.getInstance();
System.out.println(singleton);
}
}
}
結果: