Weitere aktuelle Java-Titel finden Sie bei dpunkt.
 Inhaltsverzeichnis   Auf Ebene Zurück   Seite Zurück   Seite Vor   Auf Ebene Vor   Eine Ebene höher   Index


4.16.5

Dynamische Proxies


Der Begriff Proxy dürfte vielen Lesern vom Internetzugang her bekannt sein. Es handelt sich hierbei um einen zentralen Server, der Anfragen von Clients entgegennimmt und dann delegiert.

Im Kontext der objektorientierten Programmierung hat der Begriff Proxy eine durchaus ähnliche Bedeutung: Ein Proxy ist eine Art Stellvertreterobjekt, das Methodenaufrufe an ein oder mehrere nachgeordnete Objekte delegiert. Daher ergibt sich auch ein Kapselungseffekt: Die Aufrufer »sehen« nichts davon, dass die Anfragen nicht vom Proxy selbst, sondern von den nachgeordneten Objekten bearbeitet werden. Auch brauchen die Proxy-Aufrufer die Klassen und Schnittstellen dieser nachgeordneten Objekte nicht zu kennen.

Ein Proxy kann in Java seit jeher durch eine Klasse realisiert werden, die alle Interfaces implementiert, deren Aufrufe delegiert werden sollen. Allerdings muss die Proxy-Klasse bei diesem Ansatz zur Laufzeit bereits existieren. Seit Version 1.3 können[1.3] Proxy-Klassen auch dynamisch erzeugt werden. Damit ist es möglich, die Proxy-Schnittstelle erst zur Laufzeit festzulegen. Dynamische Proxies können somit überall da eingesetzt werden, wo die Schnittstelle erst zur Laufzeit festgelegt werden kann oder die Delegation an nachgelagerte Objekte völlig dynamisch erfolgen soll.

Dynamische Proxies bestehen aus einem Exemplar der Klasse Proxy sowie einem damit assoziierten Exemplar von InvocationHandler. Beide Klassen sind im Paket java.lang.reflect definiert.

Abbildung 4.7: Struktur eines dynamischen Proxies in Java
Abbildung 4.7

Der InvocationHandler erhält die an den Proxy gerichteten Methodenaufrufe und bearbeitet sie je nach Implementierung entweder selbst oder delegiert sie an andere Objekte. Hierzu ruft das Proxy-Objekt die Methode invoke() des InvocationHandlers auf. Diese Methode hat drei Parameter:

Abbildung 4.7 stellt den Zusammenhang von Proxy und InvocationHandler noch einmal dar.

Die Klasse Proxy kann mit ihren statischen Methoden getProxyClass() und newProxyInstance() aus einer Liste von Interfaces eine Klasse bzw. ein Objekt erzeugen, das alle diese Interfaces implementiert.

Schematisch sieht die Erzeugung eines Proxy-Objekts folgendermaßen aus:
  // Erzeugung des anwenderdefinierten InvocationHandlers
  InvocationHandler handler = new MyInvocationHandler();
  // Referenz auf System-ClassLoader holen
  ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
  // Array mit Class-Objekten für die Proxy-Interfaces
  Class[] proxyInterfaces =
    new Class[]{java.awt.event.ActionListener.class};

  // Erzeugung des Proxy-Objekts
  Proxy proxy = Proxy.newInstance(sysClassLoader, proxyInterfaces,
                                  handler);
Als erster Parameter muss Proxy.newInstance() der ClassLoader übergeben werden, über den die Interface-Klassen geladen werden können. Dies ist in der Regel der System-ClassLoader. Die Interfaces, die der Proxy implementieren soll, werden als Array mit den entsprechenden Class-Objekten spezifiziert. Im Beispiel enthält dieses Array nur ein Element, nämlich das Class-Objekt für ActionListener. Der letzte Parameter ist schließlich der InvocationHandler, an den alle Aufrufe weitergeleitet werden.

Die wichtigsten Merkmale einer so erzeugten Proxy-Klasse sind: Eine hilfreiche Eigenschaft zum Debuggen ist die Tatsache, dass die Aufrufe aller Proxy-Methoden in der invoke()-Methode des InvocationHandlers zusammengeführt werden. Dies könnte z. B. nützlich sein, um Oberflächen-Events zu protokollieren. Hierzu kann man einen InvocationHandler erstellen, der in seiner invoke()-Methode sämtliche Ereignisse protokolliert:
  public class EventLogger implements InvocationHandler {
  
    public Object invoke(Object proxy, Method method, Object[] args)
         throws IllegalArgumentException {
      if (args == null)
        throw new IllegalArgumentException("Missing event object argument");
      if (! (args[0] instanceof AWTEvent))
        throw new IllegalArgumentException("Expecting event object");
      System.err.println("EVENT: "+args[0]);
      return null;
    }
  
  }
Bei der Erzeugung des zugehörigen Proxies gibt man im Konstruktor einfach die benötigten Listener-Interfaces an.
  // InvocationHandler erzeugen
  InvocationHandler logger = new EventLogger();

  // Array mit den Interfaces erzeugen,
  // die der Proxy haben soll
  Class[] listenerInterfaces = new Class[] {
    java.awt.event.ActionListener.class,
    java.awt.event.WindowListener.class};

  // Erzeugung des Proxy-Objekts
  Object proxy = Proxy.newProxyInstance(
    ClassLoader.getSystemClassLoader(),
    listenerInterfaces, logger);
Anschließend wird der Proxy bei allen Komponenten registriert, für die eine Protokollierung erfolgen soll (in diesem Fall ein Frame, der einen Button enthält):
  JFrame frame = new JFrame("Proxy Demo");
  JButton button = new JButton("OK");
  frame.getContentPane().add(button);
  ...
  // Proxy als Event-Listener registrieren
  button.addActionListener((ActionListener)proxy);
  frame.addWindowListener((WindowListener)proxy);
Es ist zu beachten, dass in den letzten beiden Zeilen ein Cast in den jeweiligen Listener-Typ durchgeführt werden muss, da proxy für den Compiler eine Referenz auf die Klasse Proxy ist. Erst zur Laufzeit referenziert proxy ein Objekt, das die entsprechenden Listener-Interfaces implementiert. Bei einem statisch (also zum Zeitpunkt des Kompilierens) definierten Proxy für die Listener-Interfaces wären die Casts dagegen nicht nötig.

In diesem Beispiel delegiert der InvocationHandler zwar keine Aufrufe, es wird aber deutlich, wie die Handhabung der Methodenaufrufe zahlreicher Listener-Interfaces mit einem dynamischen Proxy sehr elegant gelöst werden kann, da eine Abbildung auf eine einzige Methode im InvocationHandler möglich ist.

Die »statische« Alternative dazu wäre, eine Klasse zu erstellen, die alle erforderlichen Interfaces implementiert. Diese müsste allerdings jedes Mal neu kompiliert werden, wenn ein weiterer Listener-Typ unterstützt werden soll. In dem hier gezeigten Beispiel ist dagegen keine Neuübersetzung nötig. Außerdem ist eine Proxy-gestützte Lösung für diesen Zweck einfacher handhabbar, da beim herkömmlichen Ansatz schnell sehr viele Methoden zu implementieren wären.

Material zum Beispiel


 Inhaltsverzeichnis   Auf Ebene Zurück   Seite Zurück   Seite Vor   Auf Ebene Vor   Eine Ebene höher   Index

Copyright © 2002 dpunkt.Verlag, Heidelberg. Alle Rechte vorbehalten.