Dynamiske proxyer i Java

1. Introduktion

Denne artikel handler om Java's dynamiske proxyer - som er en af ​​de primære proxy-mekanismer, der er tilgængelige for os på sproget.

Kort sagt, fuldmagter er fronter eller indpakninger, der sender funktionsopkald gennem deres egne faciliteter (normalt på ægte metoder) - hvilket muligvis tilføjer en vis funktionalitet.

Dynamiske fuldmagter tillader en enkelt klasse med en enkelt metode at betjene flere metodekald til vilkårlige klasser med et vilkårligt antal metoder. En dynamisk proxy kan betragtes som en slags Facade, men en, der kan foregive at være en implementering af enhver grænseflade. Under dækslet, det dirigerer alle metodeopkald til en enkelt handler - det påberåbe sig () metode.

Selvom det ikke er et værktøj, der er beregnet til daglige programmeringsopgaver, kan dynamiske proxyer være ret nyttige for rammeskribenter. Det kan også bruges i de tilfælde, hvor konkrete klasseimplementeringer ikke kendes før kørselstid.

Denne funktion er indbygget i standard-JDK, hvorfor der ikke kræves yderligere afhængigheder.

2. Påkaldshåndterer

Lad os bygge en simpel proxy, der faktisk ikke gør andet end at udskrive, hvilken metode der blev anmodet om at blive påberåbt, og returnere et hårdt kodet nummer.

Først skal vi oprette en undertype af java.lang.reflect.InvocationHandler:

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

Her har vi defineret en simpel proxy, der logger på, hvilken metode der blev påberåbt og returnerer 42.

3. Oprettelse af proxyinstans

En proxyinstans, der betjenes af den påkaldshåndterer, vi netop har defineret, oprettes via et fabriksmetodeopkald på java.lang.reflect.Proxy klasse:

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

Når vi har en proxyinstans, kan vi påberåbe sig grænseflademetoderne som normalt:

proxyInstance.put ("hej", "verden");

Som forventet en besked om sætte() den metode, der påberåbes, udskrives i logfilen.

4. Påkaldshåndterer via Lambda Expressions

Siden InvocationHandler er en funktionel grænseflade, er det muligt at definere handler inline ved hjælp af lambda-udtryk:

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

Her definerede vi en handler, der returnerer 42 for alle get-operationer og -kast Ikke-understøttetOperationException til alt andet.

Det påberåbes på nøjagtig samme måde:

(int) proxyInstance.get ("hej"); // 42 proxyInstance.put ("hej", "verden"); // undtagelse

5. Eksempel på timing af dynamisk proxy

Lad os undersøge et potentielt virkeligt scenarie for dynamiske proxyer.

Antag, at vi vil registrere, hvor lang tid vores funktioner tager at udføre. I dette omfang definerer vi først en handler, der er i stand til at indpakke det "rigtige" objekt, spore timinginformation og reflekterende påkaldelse:

offentlig klasse TimingDynamicInvocationHandler implementerer InvocationHandler {privat statisk logger LOGGER = LoggerFactory.getLogger (TimingDynamicInvocationHandler.class); private endelige kortmetoder = ny HashMap (); privat objekt mål public TimingDynamicInvocationHandler (Object target) {this.target = target; for (Metodemetode: target.getClass (). getDeclaredMethods ()) {this.methods.put (method.getName (), metode); }} @ Override public Object invoke (Object proxy, Method method, Object [] args) throw Throwable {long start = System.nanoTime (); Objektresultat = methods.get (method.getName ()). Påberåbe sig (target, args); lang forløbet = System.nanoTime () - start; LOGGER.info ("Udførelse {} færdig med {} ns", method.getName (), forløbet); returresultat }}

Derefter kan denne proxy bruges på forskellige objekttyper:

Map mapProxyInstance = (Map) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), ny klasse [] {Map.class}, ny TimingDynamicInvocationHandler (ny HashMap ())); mapProxyInstance.put ("hej", "verden"); CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), ny klasse [] {CharSequence.class}, ny TimingDynamicInvocationHandler ("Hello World")); csProxyInstance.length ()

Her har vi tilknyttet et kort og en tegnsekvens (String).

Påkald af proxy-metoderne vil delegere til det indpakkede objekt såvel som producere loggingssætninger:

Udførelse sæt færdig i 19153 ns Udførelse færdig i 8891 ns Udførelse charAt færdig i 11152 ns Udførelse længde færdig i 10087 ns

6. Konklusion

I denne hurtige vejledning har vi undersøgt Java's dynamiske proxyer samt nogle af dens mulige anvendelser.

Som altid kan koden i eksemplerne findes på GitHub.