Jul 172007
 

Das Singleton-Pattern – meiner Erfahrung nach, ein sehr häufig verwendetes Pattern, welches nahezu jeder Entwickler in seinem Wortschatz hat. Die Implementierung des Singleton-Patterns für eine bestimmte Klasse sieht vor, dass eine statisches Feld in der Klassen die Referenz der einzigen Instanz derselben Klasse speichert. Eine Methode “getInstance” liefert diese Referenz zurück und instanziiert falls nötig die Klasse. Der dazu notwendige Code muss jedes mal von Hand implementiert werden. Mit AspectJ und einer Java Annotation soll das Erstellen von Singletons vereinfacht werden. Im folgenden wird eine Lösung vorgestellt mit der eine beliebige Klasse zum Singleton gemacht wird, indem diese die Annotation “@Singleton” erhält. Fortan liefert das Instanziieren der Klasse (“new”) immer ein und dieselbe Instanz.

Folgender Beispie-Code zeigt die eine effiziente, multi-threading fähige Implementierung des Singleton-Patterns in Java (Ursprüngliche Idee von William Pugh)

public class HighlanderClassic {

  private HighlanderClassic() {
  }

  private static class SingletonHolder {
    private final static HighlanderClassic INSTANCE = new HighlanderClassic();
  }

  public static HighlanderClassic getInstance() {
    return SingletonHolder.INSTANCE;
  }
}

Wird die statische Methode “getInstance” aufgerufen, so erfolgt falls notwendig die Instanziierung von “Highlander” über das Laden von “SingletonHolder”. Letztendlich wird die einzige Instanz der Klasse zurückgeliefert, denn es kann schließlich “nur einen geben” (Highlander!).

Singletons werden ohne Parameter erstellt. Parametrisierung macht keinen Sinn, da es eben nur eine Instanz geben kann.

Mit AspectJ ist es möglich den Aufruf von “new Highlander()” abzufangen. Dies ist der Ansatzpunkt für die Realisierung des Singletons mit AspectJ.

Soll auf die Singleton-Instanz zugegriffen werden, so wird die Instanz der Klasse “Highlander” nicht (wie fast schon gewohnt) über die statische Methode “getInstance” bezogen, sondern einfach bedenkenlos mittels “new” instanziiert. Tatsächlich wird jedoch nur eine neue Instanz von erzeugt, falls für die Klasse “Highlander” noch keine Instanz erzeugt wurde. Dieses Vorgehen kann man sicherlich kontrovers diskutieren. Dennoch denke ich, dass diese Lösung durch Einfachheit glänzt. Oft ist es aus Sicht des Singleton-Clients unnötig zu wissen, ob es sich um ein Singleton handelt oder nicht, daher finde ich die Verwendung von “new” durchaus zulässig.

Wird die Annotation “@Singleton” an eine Klasse angefügt, so wird der Instanziierung der Klasse ein Singleton-Mechanismus (nur eine Instanz der Klasse existiert) durch einen Aspect auferlegt. Wichtig ist, dass die Klasse nur einen einzigen parameterlosen öffentlichen Konstruktor besitzt (Parametrisierung macht für Singletons keinen Sinn).

Der Aspect sieht wie folgt aus:

import java.util.HashMap;
import java.util.Map;

/**
 * Every class annotated with {@link Singleton} will be treated as singleton
 * (only one instance).
 *
 * @author Marc Neumann
 */
public aspect AspectSingleton issingleton()
{
  // ~ point cuts /////////////////////////////////////////////////////////////

  private pointcut withinSingletonType() :
    within((@Singleton *)+);

  private pointcut targetSingletonType() :
    @target(Singleton);

  private pointcut execSingletonConAtLeastOneArgs() :
    execution(new(*, ..)) && withinSingletonType();

  private pointcut callSingletonConNoArg() :
    call((@Singleton *)+.new());

  // ~ declares ///////////////////////////////////////////////////////////////

  declare error : execSingletonConAtLeastOneArgs() :
    "A class marked as singleton must only have a public no argument constructor";

  // ~ aspect fields //////////////////////////////////////////////////////////

  private Map<Class, Object> mapSingletonByClass = new HashMap<Class, Object>();

  // ~ inter-type declarations ////////////////////////////////////////////////

  // ~ advices ////////////////////////////////////////////////////////////////

  Object around() : callSingletonConNoArg() {

    Class singletonClazz = thisJoinPointStaticPart.getSignature()
        .getDeclaringType();

    synchronized (mapSingletonByClass) {
      Object singletonObj = mapSingletonByClass.get(singletonClazz);

      if (singletonObj == null) {
        try {
          singletonObj = proceed();
          mapSingletonByClass.put(singletonClazz, singletonObj);
        }
        catch (InstantiationException e) {
          throw new RuntimeException(e);
        }
        catch (IllegalAccessException e) {
          throw new RuntimeException(e);
        }
      }

      assert (singletonObj != null);
      return singletonObj;
    }
  }

}

Der Aspect verwendet einen around-Advise der sich um den Aufruf des Konstruktors jeder Klasse legt, die per Annotation “@Singleton” gekennzeichnet ist. Es ist wichtig, dass als AspectJ PointCutcall((@Singleton *)+.new())” verwendet wird. Ein PointCut auf die Ausführung (execution) des Konstruktors ist für die Umsetzung des Singleton-Mechanismus nicht zweckdienlich, da zur Zeit der Ausführung des Konstruktors das Objekt der Klasse bereits instanziiert ist.

Der Aspect ist selbst ein Singleton (“public aspect AspectSingleton issingleton()”), damit nur eine Instanz des Aspects existiert und diese die Singleton-Instanzen aller Singleton-Klassen im Feld “mapSingletonByClass” verwalten kann.

Zusätzlich wird per “declare error” ein Compile-Fehler deklariert, den AspectJ ausgibt, wenn eine Singleton-Klasse einen parametrisierten Konstruktor verwendet. Wie zuvor angesprochen, darf eine Singleton-Klasse für den hier diskutierten Mechanismus nur einen parameterlosen Konstruktor besitzen. Die Sichtbarkeit des Konstruktors ist frei (public, protected, etc.)

Im Grunde ist die Realisierung von Singletons mit AspectJ kein Pattern mehr, sondern eine “Cross-Cutting-Component”. Schließlich wird nicht mustergültig (Pattern) ein bestimmtes Konzept in jeder Singleton-Klasse implementiert, sondern einfach die Annotation “@Singleton” angefügt – der Aspect erledigt den Rest.

Die Highlander-Klasse sieht als AOP-Singleton wie folgt aus:

@Singleton
public class HighlanderAop {

  public HighlanderAop() {
  }
}

Die Verwendung von beiden Highlander-Singleton-Varianten (Klassisch und AOP) sieht in einem JUnit-Test wie folgt aus:

import org.junit.Assert;
import org.junit.Test;

public class HighlanderUsage {

  @Test
  public void useClassic() {
    HighlanderClassic classic1 = HighlanderClassic.getInstance();
    HighlanderClassic classic2 = HighlanderClassic.getInstance();
    Assert.assertSame(classic1, classic2);
  }

  @Test
  public void useAop() {
    HighlanderAop aop1 = new HighlanderAop();
    HighlanderAop aop2 = new HighlanderAop();
    Assert.assertSame(aop1, aop2);
  }

}

Wie immer habe ich ein offenes Ohr für Kritik – werde jedoch kleine, nette Lösungen nicht verbal kampflos aufgeben :-)

  2 Responses to “Singleton-Pattern mit AspectJ”

  1. […] stumbled upon this post (german) that shows how to implement the singleton pattern with AspectJ. Although you may want to […]

  2. Hi,
    you could also use java.util.concurrent.ConcurrentHashMap instead of the synchronized block.
    Or should do double checking before the put operation (older JVM’s or use volatile in newer)
    synchronized(…) {
    mapSingletonByClass.put(singletonClazz, singletonObj);
    }
    ref : http://www.ibm.com/developerworks/java/library/j-dcl.html

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>