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();
}
Essayez de créer votre constructeur public
private Singleton() {
if( Singleton.singleton != null ) {
throw new InstantiationError( "Creating of this object is not allowed." );
}
}
Définissez le singleton comme ceci:
public enum Singleton {
INSTANCE
}
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.
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
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
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.
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:
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:
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.
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.
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.
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)
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();
}
}