web-dev-qa-db-fra.com

Singleton: Comment arrêter la création d'instance via Reflection

Je sais qu'en Java, nous pouvons créer une instance de classe par new, clone(), Reflection et par serializing and de-serializing.

J'ai créé une classe simple implémentant un Singleton.

Et je dois arrêter tout le chemin, on peut créer une instance de ma classe.

public class Singleton implements Serializable{
    private static final long serialVersionUID = 3119105548371608200L;
    private static final Singleton singleton = new Singleton();
    private Singleton() { }
    public static Singleton getInstance(){
        return singleton;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Cloning of this class is not allowed"); 
    }
    protected Object readResolve() {
        return singleton;
    }
    //-----> This is my implementation to stop it but Its not working. :(
    public Object newInstance() throws InstantiationException {
        throw new InstantiationError( "Creating of this object is not allowed." );
    }
}

Dans cette classe, j'ai réussi à arrêter l'instance de classe avec new, clone() et serialization, mais je ne peux pas l'arrêter par réflexion.

Mon code pour créer l'objet est

try {
    Class<Singleton> singletonClass = (Class<Singleton>) Class.forName("test.singleton.Singleton");
    Singleton singletonReflection = singletonClass.newInstance();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}
29
Talha Ahmed Khan

Essayez de créer votre constructeur public

private Singleton() {
    if( Singleton.singleton != null ) {
        throw new InstantiationError( "Creating of this object is not allowed." );
    }
}
50
Dave G

Définissez le singleton comme ceci:

public enum Singleton {
    INSTANCE
}
15
Arne Deutsch
private Singleton() { 
    if (Singleton.singleton != null) {
        throw new RuntimeException("Can't instantiate singleton twice");
    }
}

Une autre chose à surveiller est la méthode readResolve(..), car votre classe implémente Serialiable. Là vous devriez retourner l'instance existante.

Mais le moyen le plus simple d’utiliser singletons est d’énumérer - vous ne vous inquiétez pas de ces choses.

10
Bozho

Nous pouvons le casser en utilisant une classe imbriquée statique 

S'il vous plaît suivez le code ci-dessous son correct à 100%, j'ai testé

package com.singleton.breakable;

import Java.io.Serializable;

class SingletonImpl implements Cloneable, Serializable {

    public static SingletonImpl singleInstance = null;

    private SingletonImpl() {

    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return singleInstance;
    };

    public Object readResolve() {
        return SingletonImpl.getInstance(); // 
    }

    public static SingletonImpl getInstance() {

        if (null == singleInstance) {
            singleInstance = new SingletonImpl();
        }
        return singleInstance;
    }

}


package com.singleton.breakable;

import Java.io.FileInputStream;
import Java.io.FileOutputStream;
import Java.io.ObjectInputStream;
import Java.io.ObjectOutputStream;
import Java.lang.reflect.Constructor;

class FullySingletonClass {

    public static void main(String[] args) {

        SingletonImpl object1 = SingletonImpl.getInstance();
        System.out.println("Object1:" + object1);

        try {
            FileOutputStream fos = new FileOutputStream("abc.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(object1);

            FileInputStream fis = new FileInputStream("abc.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            SingletonImpl object2 = (SingletonImpl) ois.readObject();
            System.out.println("Object2" + object2);

        } catch (Exception e) {
            // TODO: handle exception
        }
        try {
            Constructor[] constructors = SingletonImpl.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                // Below code will not destroy the singleton pattern
                constructor.setAccessible(true);
                SingletonImpl Object3 = (SingletonImpl) constructor.newInstance();
                System.out.println("Object3: Break through Reflection:" + Object3);
                break;
            }
        } catch (Exception ew) {

        }

    }
}

**OUTPUT**
Object1:com.singleton.breakable.SingletonImpl@15db9742
Object2com.singleton.breakable.SingletonImpl@15db9742
Object3: Break through Reflection:com.singleton.breakable.SingletonImpl@33909752
1

Je code ci-dessous va fonctionner ..

class Test {

    static private Test t = null;
    static {
        t = new Test();
    }

    private Test(){}

    public static Test getT() {
        return t;
    }

    public String helloMethod() {
        return "Singleton Design Pattern";
    }
}


public class MethodMain {

    public static void main(String[] args) {
        Test t = Test.getT();
        System.out.println(t.helloMethod());
    }
}

sortie: Singleton Design Pattern

1
Ashish

En guise d'alternative au singleton, vous pouvez jeter un coup d'œil au motif monostate . Ensuite, l’instanciation de votre classe n’est plus un problème et vous n’aurez plus à vous soucier de aucun des scénarios que vous avez énumérés.

Dans le modèle monostate, tous les champs de votre classe sont static. Cela signifie que toutes les instances de la classe partagent le même état, comme avec un singleton. De plus, ce fait est transparent pour les appelants; ils n'ont pas besoin de connaître les méthodes spéciales comme getInstance, ils créent simplement des instances et travaillent avec elles.

Mais, comme avec singleton, c'est une forme d'état global caché; ce qui est très mauvais.

1
Jordão

En dehors de la solution enum, tous les autres peuvent être contournés via Reflexion Voici deux exemples de solution de contournement de la solution Dave G:

1: Définition de la variable Singleton.singleton sur null

Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
Constructor theConstructor = constructors[0];
theConstructor.setAccessible(true);
Singleton instance1 = (Singleton) theConstructor.newInstance();

Singleton.getInstance();

Field f1 = Singleton.class.getDeclaredField("singleton");
f1.setAccessible(true);
f1.set(f1, null);
Singleton instance2 = (Singleton) theConstructor.newInstance();

System.out.println(instance1);
System.out.println(instance2);

Sortie:

  • Singleton @ 17f6480 
  • Singleton @ 2d6e8792

2: ne pas appeler le getInstance

Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
Constructor theConstructor = constructors[0];
theConstructor.setAccessible(true);
Singleton instance1 = (Singleton) theConstructor.newInstance();
Singleton instance2 = (Singleton) theConstructor.newInstance();

System.out.println(instance1);
System.out.println(instance2);

Sortie:

  • Singleton @ 17f6480 
  • Singleton @ 2d6e8792

Donc, je peux penser à 2 façons si vous ne voulez pas aller avec un Enum:

1ère option: utilisation de securityManager:

Cela empêche d'utiliser des opérations non autorisées (appeler des méthodes privées en dehors de la classe, etc.)

Il suffit donc d’ajouter une ligne au constructeur singleton proposé par les autres réponses 

private Singleton() {
    if (singleton != null) {
        throw new IllegalStateException("Singleton already constructed");
    }
    System.setSecurityManager(new SecurityManager());
}

cela empêche l’appel de setAccessible(true)So lorsque vous voulez l’appeler:

Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
Constructor theConstructor = constructors[0];
theConstructor.setAccessible(true);

cette exception se produira: Java.security.AccessControlException: access denied ("Java.lang.RuntimePermission" "createSecurityManager") 

2nd option: Dans le constructeur singleton, testez si l'appel est effectué via Reflexion

Je vous renvoie à cet autre Stackoverflow thread pour connaître le meilleur moyen d'obtenir la classe ou la méthode de l'appelant. 

Donc, si j'ajoute ceci dans le constructeur Singleton:

String callerClassName = new Exception().getStackTrace()[1].getClassName();
System.out.println(callerClassName);

Et j'appelle ça comme ça:

Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
Constructor theConstructor = constructors[0];
theConstructor.setAccessible(true);
Singleton instance1 = (Singleton) theConstructor.newInstance();

la sortie sera: jdk.internal.reflect.DelegatingConstructorAccessorImpl

mais si je l’appelle régulièrement (en instanciant un constructeur public ou en appelant une méthode sans Reflexion), le nom de la classe de la méthode d’appel est imprimé. Ainsi, par exemple, j'ai: 

public class MainReflexion {
    public static void main(String[] args) {
        Singleton.getInstance();
    }
}

callerClassName sera MainReflexion et le résultat sera donc MainReflexion.


PS: S'il existe des solutions de contournement pour les solutions proposées, veuillez me le faire savoir.

0
ihebiheb

Pour surmonter le problème soulevé par la réflexion, des énumérations sont utilisées car Java garantit en interne que la valeur d’énumération n’est instanciée qu’une fois. Étant donné que les Java Enums sont globalement accessibles, ils peuvent être utilisés pour des singletons. Son seul inconvénient est qu’il n’est pas flexible, c’est-à-dire qu’il ne permet pas d’initialisation paresseuse.

public enum Singleton {
 INSTANCE
}

public class ReflectionTest 
{

    public static void main(String[] args)
    {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
    System.out.println("instance1 hashcode- "
                                      + instance1.hashCode());
        System.out.println("instance2 hashcode- "
                                      + instance2.hashCode());
    }
}

JVM gère la création et l’invocation des constructeurs d’énum en interne. Comme les enums ne donnent pas leur définition de constructeur au programme, il ne nous est pas possible d’y accéder également par Reflection.

Reportez-vous à l'article pour plus de détails.

0
Swati Gour

Approche avec initialisation paresseuse:

  private static Singleton singleton;

  public static Singleton getInstance() {
    if(singleton==null){
      singleton= new Singleton();
    }
    return singleton;
  }


private Singleton() {
    if (Singleton.singleton != null) {
      throw new InstantiationError("Can't instantiate singleton twice");
    }
    Singleton.singleton = this;
}

Cette approche fonctionne même si vous décidez de créer une instance en utilisant la réflexion avant toute invocation de getInstance.

0
Nikhil

Notez juste que depuis Java 8 et selon mon contrôle, vous ne pouvez pas instancier un Singleton via Reflections tant qu'il a un constructeur privé.

Vous obtiendrez cette exception:

Exception in thread "main" Java.lang.IllegalAccessException: Class com.s.Main can not access a member of class com.s.SingletonInstance with modifiers "private"
at Sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
at Java.lang.Class.newInstance(Unknown Source)
at com.s.Main.main(Main.Java:6)
0
olive_tree

Perfect Singleton Classe pouvant éviter la création d'instances pendant la sérialisation, le clone et la réflexion.

import Java.io.Serializable;

public class Singleton implements Cloneable, Serializable {

    private static final long serialVersionUID = 1L;
    private static volatile Singleton instance;

    private Singleton() {
        if (instance != null) {
            throw new InstantiationError("Error creating class");
        }
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {

                if (instance == null) {
                    return new Singleton();
                }
            }
        }
        return null;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    Object readResolve() {
        return Singleton.getInstance();
    }

}
0
ABHAY JOHRI