天天看點

反射破壞單例模式以及如何防禦

推薦:​​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);
        }
    }
}      

結果:

反射破壞單例模式以及如何防禦