Bis jetzt haben wir die Proxy-Klassen per Hand geschrieben und das ist sinnvoll, wenn jeder Proxy-Typ in Struktur und Funktion einzigartig ist. Die meisten Proxy-Implementierungen sind sich aber ähnlich. Remote-Proxys müssen alle Parameter parallelschalten und Werte hin und her senden. Java RMIs (Remote Method Invocations) generieren den Glue Code für verteilte Proxys in Form von Stubs und Skeletons.
Die Codegenerierung kann statisch oder dynamisch erfolgen. Statisch ist es weniger komfortabel, da ein zusätzlicher Schritt im Build-Prozess erforderlich ist. Dynamische Codegenerierung erzeugt zur Laufzeit eine neue Proxy-Klasse, die sehr bequem und flexibel ist.
Dynamische Proxies werden mit der statischen Factory-Methode newProxyInstance() mit diesen drei Parametern konstruiert:
ClassLoader loader: Hier wird unsere neue Proxy-Klasse hineingeladen. Der ClassLoader muss in der Lage sein, die Interfaces zu sehen, die wir implementieren wollen. Der Class Loader Bootstrap kennt beispielsweise die Java-Schnittstellen (java.lang.Runnable, java.util.Collection usw.), aber nicht die Anwendungsschnittstellen. Unsere Klassen werden mit dem Class Loader System (auch AppClassLoader genannt) geladen.
Class<?>[] Interfaces ist ein Array mit allen Schnittstellen, die unser Proxy-Objekt implementieren müssen.
InvocationHandler ist eine funktionale Schnittstelle, die aufgerufen wird, wenn eine Methode das Proxy-Objekt aufruft.
Eines der am häufigsten zitierten Muster der GoF ist die sogenannte Factory. Das einzige Problem ist, dass es im Buch kein Factory Pattern gibt. Die GoF beschreiben eine Factory Method, eine abstrakte Methode zur Initialisierung von Objekten, wobei die Konstruktion an Unterklassen delegiert wird. Ein Beispiel ist die Iterator()-Methode, bei der ArrayList ein ArrayList$Itr und LinkedList ein LinkedList$ListItr erzeugt.
Die GoF beschreiben auch eine abstrakte Factory zur Erstellung von Familien verwandter Produkte. Ein Beispiel ist java.awt.Toolkit, das GUI-Komponenten für jedes Betriebssystem erstellt.
Das Pattern, das die meisten Programmierer als Factory betrachten, ist eine statische Methode zur Erstellung komplexer Objekte. Wir werden es eine statische Factory-Methode nennen. Beispiele sind im JDK reichlich vorhanden: Arrays.asList(), Proxy.newProxyInstance(), Connection.getConnection(), ByteBuffer.allocateDirect(), usw.
Der Proxy ist hinsichtlich der Parameter, die er in der neuen Methode ProxyInstance() akzeptiert, restriktiv. Jede falsche Kombination wird mit IllegalArgumentException begrüßt, wobei die Nachricht Hinweise auf die Fehlerursache gibt. Was ist zum Beispiel falsch am im folgenden Listing gezeigten Aufruf von Proxy.newProxyInstance()?
Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class<?>[] {CustomMap.class},
(proxy, method, args) -> null
);
Einmal ausgeführt, sehen wir das folgende Ergebnis:
java.lang.IllegalArgumentException: eu.javaspecia...CustomMap
referenced from a method is not visible from class loader
Map.class ist eine JDK-Klasse, also vom Bootstrap Class Loader geladen, der unsere CustomMap nicht kennt. Stattdessen sollten wir den Class Loader verwenden, der unsere CustomMap geladen hat, hier den System Class Loader:
Proxy.newProxyInstance(
CustomMap.class.getClassLoader(),
new Class<?>[] {CustomMap.class},
(proxy, method, args) -> null
);
Einzelheiten finden Sie in den JavaDocs des Proxys [1]. Der dynamische Proxy wird auf magische Weise in denjenigen Class Loader injiziert, den wir spezifizieren. Wir müssen sicherstellen, dass es der richtige ist, insbesondere bei Verwendung eines Anwendungsservers.
Das Java Platform Module System (JPMS) kann auch für dynamische Proxies genutzt werden. Proxy-Klassen haben die gleichen Einschränkungen wie die von ihnen implementierten Schnittstellen. Wenn beispielsweise eine der Schnittstellen nicht öffentlich ist, wird die Proxy-Klasse im gleichen Paket wie diese Schnittstelle angelegt. Ist eine der Proxy-Schnittstellen in einem Paket enthalten, das nicht exportiert werden kann und nicht offen ist, wird die Proxy-Klasse diese Eigenschaften übernehmen und möglicherweise in einem dynamischen Modul erstellt werden, sofern alle Schnittstellen öffentlich sind. Mit der Umgebungsvariable -Djdk.proxy.debug=debug können wir Debug-Informationen darüber ausgeben, wie der dynamische Proxy durch das JPMS erstellt wird. Weitere Einzelheiten zu den Regeln für dynamische Proxies und JPMS können in den JavaDocs zur Proxy-Mitgliedschaft nachgelesen werden [2].
17. – 20. März 2020, München | Refactoring + Design Patterns
mit Dr. Heinz Kabutz
Mit den zwei neuen Intensivtrainings werden Ihnen umfassendes und aktuelles Know-how zu Refactoring (Java 8 Streams and Lambdas) und Design Pattens vermittelt. An einem Tag erleben Sie intensives Refactoring einer typischen Geschäftsanwendung. Anschließend decken Sie im 4-tägigen Design-Patterns-Seminar 30 Designmuster ab, die im alltäglichen Java-Coding weit verbreitet sind. Es sind einzigartige Hands-on-Trainings mit hervorragend strukturierten Vorträgen und einer Fülle an praktischen Übungen.
Trainer des Camps ist Dr. Heinz Kabutz , einer der international bedeutendsten Java-Experten und Autor des weltweit bekannten „The Java Specialists’ Newsletter“ (www.javaspecialists.eu).
Mehr InformationenJede Methode, die auf dem Proxy-Objekt aufgerufen wird, wird an die Methode invoke() vom InvocationHandler gesendet:
public interface InvocationHandler {
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable;
}
Die Methode invoke() hat drei Parameter:
Object proxy: Instanz der dynamischen Proxy-Klasse, die die Methode invoke() aufruft.
Method method: Objekt von java.lang.reflect.Method für die aufgerufene Methode. Die Methode stammt entweder von einer der Schnittstellen, die zum Erstellen des dynamischen Proxys verwendet werden, oder von einer der drei öffentlichen, nicht endgültigen Methoden von Object, d. h. hashCode(), equals(Object) oder toString().
Object[] args: ein Array der Parameter, die an die Methode übergeben werden. Wird null sein, wenn die Methode keine Parameter hat.
Die Methode invoke() deklariert, dass sie Throwable ausgibt, jedoch sollten Methoden nur die geprüften Exceptions auslösen, die in ihrer Signatur deklariert sind.
Lassen Sie uns nun einen Blick darauf werfen, wie wir unseren eigenen InvocationHandler erstellen können. In unserem LoggingInvocationHandler (Listing 1) protokollieren...