Es gibt einfache und effiziente Möglichkeiten, das Singleton-Muster zu implementieren, aber es gibt keine Möglichkeit, die Integrität des Singletons unter allen Umständen sicherzustellen.
Das Singleton-Muster bedeutet, dass eine Klasse nur einmal instanziiert wird und zur Darstellung globaler oder systemweiter Komponenten verwendet wird. Das Singleton-Muster wird häufig für Protokollierung, Fabriken, Fenstermanager, Plattformkomponentenverwaltung usw. verwendet. Ich denke, dass das Singleton-Muster so weit wie möglich vermieden werden sollte, da es nach der Implementierung schwierig zu ändern oder zu überlasten ist und Probleme wie Schwierigkeiten beim Schreiben von Testfällen und eine schlechte Codestruktur verursachen wird. Außerdem ist das Singleton-Muster im folgenden Artikel unsicher.
Die Leute investieren viel Energie in die Erforschung, wie sie das Singleton-Muster besser implementieren können, aber es gibt eine einfache und effiziente Implementierungsmethode. Es gibt jedoch keine Möglichkeit, die Integrität eines Singletons unter allen Umständen sicherzustellen. Lesen Sie weiter unten und prüfen Sie, ob Sie damit einverstanden sind.
Endgültiges Feld
Diese Methode privatisiert den Konstruktor und stellt ein öffentliches statisches Endobjekt bereit:
public class FooSingleton { public final static FooSingleton INSTANCE = new FooSingleton(); private FooSingleton() { } public void bar() { }}
Wenn die Klasse geladen wird, wird das statische Objekt initialisiert Punkt, an dem der private Konstruktor zum ersten und letzten Mal aufgerufen wird. Selbst wenn mehrere Threads diese Klasse aufrufen, bevor die Klasse initialisiert wird, kann die JVM garantieren, dass die Klasse vollständig initialisiert ist, wenn die Threads weiter ausgeführt werden. Mithilfe von Reflection und der setAccessible(true)-Methode ist es jedoch möglich, andere neue Instanzen zu erstellen:
Constructor[] constructors = FooSingleton.class.getDeclaredConstructors(); Constructor constructor = constructors[0]; constructor.setAccessible(true); FooSingleton spuriousFoo = (FooSingleton) constructor.newInstance(new Object[0]);
Wir müssen den Konstruktor so ändern, dass er nicht mehrmals aufgerufen wird, z. B. wenn er ausgelöst wird heißt wieder abnormal. Durch Ändern des FooSingleton-Konstruktors wie folgt können solche Angriffe verhindert werden:
public class FooSingleton2 { private static boolean INSTANCE_CREATED; public final static FooSingleton2 INSTANCE = new FooSingleton2(); private FooSingleton2() { if (INSTANCE_CREATED) { throw new IllegalStateException("You must only create one instance of this class"); } else { INSTANCE_CREATED = true; } } public void bar() { } }
Dies mag sicherer erscheinen, aber tatsächlich ist es immer noch genauso einfach, eine neue Instanz zu erstellen. Wir müssen nur das Feld INSTANCE_CREATED ändern und den gleichen Trick noch einmal spielen:
Field f = FooSingleton2.class.getDeclaredField("INSTANCE_CREATED"); f.setAccessible(true); f.set(null, false); Constructor[] constructors = FooSingleton2.class.getDeclaredConstructors(); Constructor constructor = constructors[0]; constructor.setAccessible(true); FooSingleton2 spuriousFoo = (FooSingleton2) constructor.newInstance(new Object[0]);
Alle von uns getroffenen Vorsichtsmaßnahmen können umgangen werden, daher ist diese Lösung nicht realisierbar.
Statische Fabrik
Mit dieser Methode ähneln öffentliche Mitglieder statischen Fabriken:
public class FooSingleton3 { public final static FooSingleton3 INSTANCE = new FooSingleton3(); private FooSingleton3() { } public static FooSingleton3 getInstance() { return INSTANCE; } public void bar() { } }
Die Methode getInstance() gibt immer die gleiche Objektreferenz zurück. Obwohl diese Lösung Reflexionen nicht verhindern kann, bietet sie dennoch einige Vorteile. Sie können beispielsweise die Implementierung eines Singletons ändern, ohne die API zu ändern. getInstance() kommt in fast allen Singleton-Implementierungen vor und signalisiert auch, dass es sich tatsächlich um ein Singleton-Muster handelt.
Lazy Loading Singleton-Modus
(Anmerkung des Übersetzers: In der Softwareentwicklung bezieht sich die Redewendung Initialization-on-Demand Holder auf den Lazy Loading Singleton-Modus, siehe Wikipedia)
If Wenn Sie die Erstellung eines Singletons so weit wie möglich verzögern möchten (Lazy Loading), können Sie die Methode Lazy Initialization verwenden, um beim ersten Aufruf der Methode getInstance() threadsicher einen Singleton zu erstellen. Im Vergleich zur vorherigen Lösung, bei der beim ersten Verweis auf die Klasse ein Singleton erstellt wird (Hungry-Style-Loading), ist dies eine Verbesserung. Wie folgt:
public class FooSingleton4 { private FooSingleton4() { } public static FooSingleton4 getInstance() { return FooSingleton4Holder.INSTANCE; } private static class FooSingleton4Holder { private static final FooSingleton4 INSTANCE = new FooSingleton4(); } }
Seien Sie vorsichtig mit der Serialisierung
Wenn ein Singleton die Serialisierung implementiert, ist er einer weiteren Bedrohung ausgesetzt. Daher müssen Sie alle Felder als transient deklarieren (damit sie nicht serialisiert werden) und eine benutzerdefinierte readResolve()-Methode bereitstellen, die einen Verweis auf die eindeutige Instanz INSTANCE zurückgibt.
Aufzählung
Hier verwenden wir die Aufzählung als Container der Singleton-INSTANZ:
public enum FooEnumSingleton { INSTANCE; public static FooEnumSingleton getInstance() { return INSTANCE; } public void bar() { } }
Gemäß Java Language Specification 8.9 „stellt die endgültige Klonmethode von Enum sicher, dass die Aufzählung funktioniert hält ewig Es kann nicht geklont werden und sein spezieller Serialisierungsmechanismus stellt sicher, dass das kopierte Objekt nicht deserialisiert werden kann. Gleichzeitig wird die Verwendung von Reflektion zum Instanziieren der Aufzählung verhindert, wodurch sichergestellt wird, dass diese vier Aspekte außer bei Aufzählungskonstanten nicht auftreten . Es gibt andere Aufzählungsinstanzen desselben Typs.“
Auf diese Weise scheinen wir leicht vor Serialisierungs-, Klon- und Reflexionsangriffen geschützt zu sein. Als ich diese Passage zum ersten Mal sah, wollte ich sofort beweisen, dass sie falsch ist. Wie im folgenden Code gezeigt, ist es einfach, diese Schutzmaßnahmen zu umgehen:
Constructor con = FooEnumSingleton.class.getDeclaredConstructors()[0]; Method[] methods = con.getClass().getDeclaredMethods(); for (Method method : methods) { if (method.getName().equals("acquireConstructorAccessor")) { method.setAccessible(true); method.invoke(con, new Object[0]); } } Field[] fields = con.getClass().getDeclaredFields(); Object ca = null; for (Field field : fields) { if (field.getName().equals("constructorAccessor")) { field.setAccessible(true); ca = field.get(con); } } Method method = ca.getClass().getMethod("newInstance", new Class[]{Object[].class}); method.setAccessible(true); FooEnumSingleton spuriousEnum = (FooEnumSingleton) method.invoke(ca, new Object[]{new Object[]{"SPURIOUS_INSTANCE", 1}}); printInfo(FooEnumSingleton.INSTANCE); printInfo(spuriousEnum); }private static void printInfo(FooEnumSingleton e) { System.out.println(e.getClass() + ":" + e.name() + ":" + e.ordinal()); }
Wenn Sie diesen Code ausführen, erhalten Sie das Ergebnis:
class com.blogspot.minborgsjavapot.singleton.FooEnumSingleton:INSTANCE:0 class com.blogspot.minborgsjavapot.singleton.FooEnumSingleton:SPURIOUS_INSTANCE:1
Der Nachteil einer Aufzählung besteht darin, dass sie kann nicht von einer anderen Basisklassenvererbung kopiert werden, da sie bereits von java.lang.Enum erbt. Wenn Sie diese Art der Vererbung simulieren möchten, können Sie sich auf das in meinem anderen Artikel vorgestellte Mixin-Muster beziehen.
Ein Vorteil von Aufzählungen besteht darin, dass Sie, wenn Sie später einen „Dualton“ oder „Trigleton“ haben möchten, nur eine neue Aufzählungsinstanz hinzufügen müssen. Nachdem Sie beispielsweise über einen Singleton-Cache verfügen, möchten Sie möglicherweise auch mehrere Ebenen in den Cache einführen.
Fazit
Obwohl es nicht einfach ist, diesen Schutz von Singletons zu umgehen, gibt es wirklich keine narrensichere Lösung. Wenn Sie eine bessere Lösung haben, lassen Sie es mich gerne wissen!
Aufzählung ist eine einfache und effiziente Möglichkeit, das Singleton-Muster zu implementieren. Wenn Sie Vererbung oder verzögertes Laden wünschen, ist die verzögerte Initialisierung eine gute Wahl.
Viel Glück mit deinen Singletons!