Dynamische Proxies in Java

1. Einleitung

In diesem Artikel geht es um die dynamischen Proxys von Java - einer der wichtigsten Proxy-Mechanismen, die uns in der Sprache zur Verfügung stehen.

Einfach ausgedrückt, Proxys sind Fronten oder Wrapper, die den Funktionsaufruf über ihre eigenen Einrichtungen (normalerweise an reale Methoden) weiterleiten und möglicherweise einige Funktionen hinzufügen.

Dynamische Proxys ermöglichen es einer einzelnen Klasse mit einer einzelnen Methode, mehrere Methodenaufrufe an beliebige Klassen mit einer beliebigen Anzahl von Methoden zu bedienen. Ein dynamischer Proxy kann als eine Art Fassade betrachtet werden , die jedoch vorgeben kann, eine Implementierung einer beliebigen Schnittstelle zu sein. Unter dem Deckmantel werden alle Methodenaufrufe an einen einzelnen Handler weitergeleitet - die invoke () -Methode.

Während es kein Tool für alltägliche Programmieraufgaben ist, können dynamische Proxys für Framework-Writer sehr nützlich sein. Es kann auch in Fällen verwendet werden, in denen konkrete Klassenimplementierungen erst zur Laufzeit bekannt sind.

Diese Funktion ist in das Standard-JDK integriert, daher sind keine zusätzlichen Abhängigkeiten erforderlich.

2. Aufrufhandler

Lassen Sie uns einen einfachen Proxy erstellen, der nichts anderes tut, als zu drucken, welche Methode aufgerufen werden soll, und eine fest codierte Nummer zurückzugeben.

Zuerst müssen wir einen Subtyp von java.lang.reflect.InvocationHandler erstellen :

public class DynamicInvocationHandler implements InvocationHandler { private static Logger LOGGER = LoggerFactory.getLogger( DynamicInvocationHandler.class); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { LOGGER.info("Invoked method: {}", method.getName()); return 42; } }

Hier haben wir einen einfachen Proxy definiert, der protokolliert, welche Methode aufgerufen wurde, und 42 zurückgibt.

3. Proxy-Instanz erstellen

Eine Proxy-Instanz, die von dem soeben definierten Aufrufhandler bedient wird, wird über einen Factory-Methodenaufruf für die Klasse java.lang.reflect.Proxy erstellt :

Map proxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, new DynamicInvocationHandler());

Sobald wir eine Proxy-Instanz haben, können wir ihre Schnittstellenmethoden wie gewohnt aufrufen:

proxyInstance.put("hello", "world");

Wie erwartet wird eine Meldung über die aufgerufene put () -Methode in der Protokolldatei ausgedruckt.

4. Invocation Handler über Lambda Expressions

Da InvocationHandler eine funktionale Schnittstelle ist, kann der Handler inline mithilfe des Lambda-Ausdrucks definiert werden:

Map proxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, (proxy, method, methodArgs) -> { if (method.getName().equals("get")) { return 42; } else { throw new UnsupportedOperationException( "Unsupported method: " + method.getName()); } });

Hier haben wir einen Handler definiert, der für alle get-Operationen 42 zurückgibt und für alles andere UnsupportedOperationException auslöst .

Es wird genauso aufgerufen:

(int) proxyInstance.get("hello"); // 42 proxyInstance.put("hello", "world"); // exception

5. Timing Dynamic Proxy Beispiel

Lassen Sie uns ein potenzielles reales Szenario für dynamische Proxys untersuchen.

Angenommen, wir möchten aufzeichnen, wie lange die Ausführung unserer Funktionen dauert. Insofern definieren wir zunächst einen Handler, der das „echte“ Objekt umschließen, Timing-Informationen verfolgen und reflektieren kann:

public class TimingDynamicInvocationHandler implements InvocationHandler { private static Logger LOGGER = LoggerFactory.getLogger( TimingDynamicInvocationHandler.class); private final Map methods = new HashMap(); private Object target; public TimingDynamicInvocationHandler(Object target) { this.target = target; for(Method method: target.getClass().getDeclaredMethods()) { this.methods.put(method.getName(), method); } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.nanoTime(); Object result = methods.get(method.getName()).invoke(target, args); long elapsed = System.nanoTime() - start; LOGGER.info("Executing {} finished in {} ns", method.getName(), elapsed); return result; } }

Anschließend kann dieser Proxy für verschiedene Objekttypen verwendet werden:

Map mapProxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, new TimingDynamicInvocationHandler(new HashMap())); mapProxyInstance.put("hello", "world"); CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { CharSequence.class }, new TimingDynamicInvocationHandler("Hello World")); csProxyInstance.length()

Hier haben wir eine Map und eine Zeichenfolge (String) als Proxy verwendet.

Aufrufe der Proxy-Methoden delegieren an das umschlossene Objekt und erzeugen Protokollierungsanweisungen:

Executing put finished in 19153 ns Executing get finished in 8891 ns Executing charAt finished in 11152 ns Executing length finished in 10087 ns

6. Fazit

In diesem kurzen Tutorial haben wir die dynamischen Proxys von Java sowie einige seiner möglichen Verwendungen untersucht.

Wie immer ist der Code in den Beispielen auf GitHub zu finden.