2014-05-03 18 views
8

Ich wurde inspiriert von this stackoverflow questionWie erstelle ich einen JVM-Global Singleton?

Wie kann man eine Java-Klasseninstanz erstellen, die garantiert nur einmal für den gesamten JVM-Prozess verfügbar ist? Jede Anwendung, die auf dieser JVM ausgeführt wird, sollte dann in der Lage sein, diese Singleton-Instanz zu verwenden.

+7

Sie können diese Garantie aufgrund all der benutzerdefinierten Klassenlader, die diese Anwendungen verwenden, nicht haben. –

+1

Wenn Sie versuchen, den Zugriff auf ein Objekt zu beschränken, müssen Sie eine physische Partition verwenden. Hier ist ein guter Artikel über "Wann ist ein Singleton kein Singleton?" http://www.oracle.com/technetwork/articles/java/singleton-1577166.html Es gibt viele Möglichkeiten, Singletons zu bekommen, die keine Singletons sind. – mttdbrd

+0

Dies hängt vollständig von Ihrer Ausführungsumgebung ab. Sie verwenden besser eine Art Datei- oder OS-Jobattribut, um die persistenten Werte zu speichern. –

Antwort

11

Sie können tatsächlich solch ein Singleton implementieren. Das Problem, das Ihnen in den Kommentaren beschrieben wurde, ist die Möglichkeit, dass eine Klasse von mehreren ClassLoader s geladen wird. Jeder dieser ClassLoaders kann dann eine Klasse mit identischem Namen definieren, die fälschlicherweise als eindeutig angenommen wird.

Sie können dies jedoch vermeiden, indem Sie einen Accessor zu Ihrem Singleton implementieren, der explizit darauf angewiesen ist, einen bestimmten ClassLoader für eine Klasse eines Vornamens zu überprüfen, die wiederum Ihren Singleton enthält. Auf diese Weise können Sie vermeiden, dass eine Singleton-Instanz von zwei verschiedenen ClassLoader s bereitgestellt wird und die Instanz dupliziert, die in der gesamten JVM eindeutig sein muss.

Aus Gründen, die später erläutert werden, teilen wir die Singleton und die SingletonAccessor in zwei verschiedene Klassen auf. Für die folgende Klasse, müssen wir später darauf achten, dass wir Zugriff auf immer durch eine bestimmtes ClassLoader mit:

package pkg; 
class Singleton { 
    static volatile Singleton instance; 
} 

Ein bequemes ClassLoader für diese Angelegenheit ist der Systemklassenlader. Der Systemklassenlader kennt alle Klassen im JVM-Klassenpfad und hat per Definition die Erweiterung und die Bootstrap-Klassenlader als Eltern. Diese beiden Klassenladeprogramme kennen normalerweise keine domänenspezifischen Klassen wie unsere Singleton-Klasse. Dies schützt uns vor unerwünschten Überraschungen. Außerdem wissen wir, dass es in einer laufenden Instanz der JVM global zugänglich und bekannt ist.

Nehmen wir jetzt an, dass die Singleton Klasse auf dem Klassenpfad ist. Auf diese Weise können wir die Instanz dieses Accessor mit Reflexion erhalten:

class SingletonAccessor { 
    static Object get() { 
    Class<?> clazz = ClassLoader.getSystemClassLoader() 
           .findClass("pkg.Singleton"); 
    Field field = clazz.getDeclaredField("instance"); 
    synchronized (clazz) { 
     Object instance = field.get(null); 
     if(instance == null) { 
     instance = clazz.newInstance(); 
     field.set(null, instance); 
     } 
     return instance; 
    } 
    } 
} 

Durch die Angabe, dass wir ausdrücklich pkg.Singleton von der Systemklassenlader geladen werden soll, stellen wir sicher, dass wir die gleiche Instanz trotz deren Klasse immer erhalten Loader geladen unsere SingletonAccessor. Im obigen Beispiel stellen wir zusätzlich sicher, dass Singleton nur einmal instanziiert wird. Alternativ können Sie die Instanziierungslogik selbst in die Klasse Singleton einfügen und die nicht verwendeten Instanzen verwerfen lassen, falls andere Klassen jemals geladen werden.

Es gibt jedoch einen großen Nachteil. Sie vermissen alle Möglichkeiten der Typsicherheit, da Sie nicht davon ausgehen können, dass Ihr Code immer von einer ClassLoader ausgeführt wird, die das Laden der Klasse Singleton an den Systemklassenlader delegiert. Dies trifft insbesondere auf eine Anwendung zu, die auf einem Anwendungsserver ausgeführt wird, der häufig Child-first-Semantiken für seine Klassenladeprogramme implementiert und nicht fragt, ob der Systemklassenlader für bekannte Typen, aber zuerst versucht, seine eigenen Typen zu laden.Beachten Sie, dass eine Laufzeittyp durch zwei Merkmale gekennzeichnet:

  1. Sein vollqualifizierten Namen
  2. Seine ClassLoader

Aus diesem Grund muss die SingletonAccessor::get Methode Object statt Singleton zurückzukehren.

Ein weiterer Nachteil ist die Tatsache, dass der Singleton Typ auf dem Klassenpfad gefunden werden muss, damit dies funktioniert. Andernfalls kennt der Systemklassenlader diesen Typ nicht. Wenn Sie den Typ Singleton auf den Klassenpfad setzen können, sind Sie hier fertig. Keine Probleme.

Wenn Sie dies nicht passieren können, gibt es jedoch einen anderen Weg, zum Beispiel meine code generation library Byte Buddy. Mit Hilfe dieser Bibliothek können wir einfach definieren eine solche Art zur Laufzeit und injizieren sie in das System Klassenlader:

new ByteBuddy() 
    .subclass(Object.class) 
    .name("pkg.Singleton") 
    .defineField("instance", Object.class, Ownership.STATIC) 
    .make() 
    .load(ClassLoader.getSytemClassLoader(), 
     ClassLoadingStrategy.Default.INJECTION) 

Sie definiert nur eine Klasse pkg.Singleton für die Systemklassenlader und die obige Strategie ist wieder anwendbar.

Sie können auch die Typsicherheitsprobleme vermeiden, indem Sie einen Wrappertyp implementieren. Sie können dies auch mit Hilfe von Byte Buddy automatisieren:

new ByteBuddy() 
    .subclass(Singleton.class) 
    .method(any()) 
    .intercept(new Object() { 
    @RuntimeType 
    Object intercept(@Origin Method m, 
        @AllArguments Object[] args) throws Exception { 
     Object singleton = SingletonAccessor.get(); 
     return singleton.getClass() 
     .getDeclaredMethod(m.getName(), m.getParameterTypes()) 
     .invoke(singleton, args); 
    } 
    }) 
    .make() 
    .load(Singleton.class.getClassLoader(), 
     ClassLoadingStrategy.Default.INJECTION) 
    .getLoaded() 
    .newInstance(); 

Sie haben soeben einen delegator erstellt, das alle Methoden der Singleton Klasse überschreibt und die Delegierten ihre Anrufung Anrufungen der JVM-global Singleton-Instanz. Beachten Sie, dass wir die reflektiven Methoden neu laden müssen, obwohl sie signaturidentisch sind, da wir uns nicht darauf verlassen können, dass die ClassLoader s des Delegaten und die JVM-globalen Klassen identisch sind.

In der Praxis möchten Sie möglicherweise die Aufrufe an SingletonAccessor.get() zwischenspeichern und vielleicht sogar die reflektiven Methoden-Lookups (die im Vergleich zu den reflektiven Methodenaufrufen ziemlich teuer sind). Dieser Bedarf hängt jedoch stark von Ihrer Anwendungsdomäne ab. Wenn Sie Probleme mit Ihrer Konstruktorhierarchie haben, können Sie die Methodensignaturen auch in eine Schnittstelle ausklammern und diese Schnittstelle sowohl für den obigen Accessor als auch für Ihre Klasse Singleton implementieren.