Dynamische proxy's in Java

1. Inleiding

Dit artikel gaat over Java's dynamische proxy's - wat een van de primaire proxy-mechanismen is die voor ons beschikbaar zijn in de taal.

Simpel gezegd, proxy's zijn fronten of wrappers die functie-aanroep doorgeven via hun eigen faciliteiten (meestal op echte methoden) - waardoor mogelijk enige functionaliteit wordt toegevoegd.

Met dynamische proxy's kan één enkele klasse met één enkele methode meerdere methodeaanroepen naar willekeurige klassen uitvoeren met een willekeurig aantal methoden. Een dynamische proxy kan worden gezien als een soort Facade, maar een die zich kan voordoen als een implementatie van elke interface. Onder de dekking, het routeert alle methode-aanroepen naar een enkele handler - de beroep doen op() methode.

Hoewel het geen hulpmiddel is dat bedoeld is voor alledaagse programmeertaken, kunnen dynamische proxy's behoorlijk nuttig zijn voor schrijvers van frameworks. Het kan ook worden gebruikt in die gevallen waarin concrete klasse-implementaties pas bekend zijn tijdens runtime.

Deze functie is ingebouwd in de standaard JDK, daarom zijn er geen extra afhankelijkheden vereist.

2. Aanroephandler

Laten we een eenvoudige proxy bouwen die eigenlijk niets anders doet dan afdrukken welke methode gevraagd werd om aangeroepen te worden en een hard gecodeerd nummer retourneren.

Eerst moeten we een subtype maken van java.lang.reflect.InvocationHandler:

openbare klasse DynamicInvocationHandler implementeert InvocationHandler {privé statische Logger LOGGER = LoggerFactory.getLogger (DynamicInvocationHandler.class); @Override public Object invoke (Object proxy, Method method, Object [] args) gooit Throwable {LOGGER.info ("Invoked method: {}", method.getName ()); terug 42; }}

Hier hebben we een eenvoudige proxy gedefinieerd die logt welke methode werd aangeroepen en 42 retourneert.

3. Proxy-instantie maken

Een proxy-instantie die wordt bediend door de aanroephandler die we zojuist hebben gedefinieerd, wordt gemaakt via een fabrieksmethode-aanroep op de java.lang.reflect.Proxy klasse:

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

Zodra we een proxy-instantie hebben, kunnen we de interfacemethoden zoals normaal aanroepen:

proxyInstance.put ("hallo", "wereld");

Zoals verwacht komt er een bericht over leggen() methode die wordt aangeroepen, wordt afgedrukt in het logbestand.

4. Invocation Handler via Lambda Expressions

Sinds InvocationHandler is een functionele interface, het is mogelijk om de handler inline te definiëren met behulp van lambda-expressie:

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

Hier hebben we een handler gedefinieerd die 42 retourneert voor alle get-operaties en worpen UnsupportedOperationException voor al het andere.

Het wordt op precies dezelfde manier aangeroepen:

(int) proxyInstance.get ("hallo"); // 42 proxyInstance.put ("hallo", "wereld"); // uitzondering

5. Timing Dynamic Proxy Voorbeeld

Laten we eens kijken naar een potentieel realistisch scenario voor dynamische proxy's.

Stel dat we willen registreren hoe lang het duurt voordat onze functies worden uitgevoerd. In dit opzicht definiëren we eerst een handler die in staat is om het 'echte' object te omhullen, timinginformatie en reflecterende aanroep te volgen:

openbare klasse TimingDynamicInvocationHandler implementeert InvocationHandler {privé statische Logger LOGGER = LoggerFactory.getLogger (TimingDynamicInvocationHandler.class); private final Map-methoden = nieuwe HashMap (); doel van een privé-object; openbare TimingDynamicInvocationHandler (Objectdoel) {this.target = target; voor (Methode methode: target.getClass (). getDeclaredMethods ()) {this.methods.put (method.getName (), methode); }} @Override public Object invoke (Object proxy, Method method, Object [] args) gooit Throwable {long start = System.nanoTime (); Object resultaat = methods.get (method.getName ()). Invoke (target, args); lang verstreken = System.nanoTime () - start; LOGGER.info ("Uitvoering {} voltooid in {} ns", method.getName (), verstreken); resultaat teruggeven; }}

Vervolgens kan deze proxy op verschillende objecttypes worden gebruikt:

Map mapProxyInstance = (Map) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), nieuwe klasse [] {Map.class}, nieuwe TimingDynamicInvocationHandler (nieuwe HashMap ())); mapProxyInstance.put ("hallo", "wereld"); CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), nieuwe klasse [] {CharSequence.class}, nieuwe TimingDynamicInvocationHandler ("Hallo wereld")); csProxyInstance.length ()

Hier hebben we een kaart en een tekenreeks (String) proxied.

Aanroepen van de proxy-methoden delegeren naar het ingepakte object en produceren ook logboekinstructies:

Uitvoering put voltooid in 19153 ns Uitvoering klaar in 8891 ns Uitvoering karakt voltooid in 11152 ns Uitvoeringslengte voltooid in 10087 ns

6. Conclusie

In deze korte tutorial hebben we de dynamische proxy's van Java onderzocht, evenals enkele van de mogelijke toepassingen ervan.

Zoals altijd is de code in de voorbeelden te vinden op GitHub.