Dynamic Proxies i Java
1. Introduksjon
Denne artikkelen handler om Java's dynamiske proxyer - som er en av de primære proxy-mekanismene som er tilgjengelige for oss på språket.
Enkelt sagt, fullmakter er fronter eller innpakninger som sender funksjonstilkalling gjennom sine egne fasiliteter (vanligvis på virkelige metoder) - noe som potensielt gir litt funksjonalitet.
Dynamiske fullmakter tillater en enkelt klasse med en enkelt metode å betjene flere metodeanrop til vilkårlige klasser med et vilkårlig antall metoder. En dynamisk proxy kan betraktes som en slags Fasade, men en som kan late som å være en implementering av hvilket som helst grensesnitt. Under dekselet, den dirigerer alle metodeanrop til en enkelt behandler - den påkalle () metode.
Selv om det ikke er et verktøy ment for hverdagsprogrammeringsoppgaver, kan dynamiske proxyer være ganske nyttige for rammeskribenter. Den kan også brukes i de tilfeller der konkrete klasseimplementeringer ikke vil være kjent før kjøretiden.
Denne funksjonen er innebygd i standard JDK, og det er derfor ikke behov for flere avhengigheter.
2. Påkallerhandler
La oss bygge en enkel proxy som faktisk ikke gjør noe annet enn å skrive ut hvilken metode som ble bedt om å bli påkalt, og returnere et hardkodet nummer.
Først må vi lage en undertype av 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 ()); retur 42; }}
Her har vi definert en enkel proxy som logger hvilken metode som ble påkalt og returnerer 42.
3. Opprette proxy-forekomst
En proxy-forekomst som betjenes av påkallingsbehandleren vi nettopp har definert, opprettes via en fabrikkmetodeanrop på java.lang.reflect.Proxy klasse:
Map proxyInstance = (Map) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), new Class [] {Map.class}, new DynamicInvocationHandler ());
Når vi har en proxy-forekomst, kan vi påberope grensesnittmetodene som normalt:
proxyInstance.put ("hei", "verden");
Som forventet en melding om sette() metoden som påberopes skrives ut i loggfilen.
4. Innkallingsbehandler via Lambda Expressions
Siden InvocationHandler er et funksjonelt grensesnitt, er det mulig å definere handler inline ved hjelp av lambda-uttrykk:
Map proxyInstance = (Map) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), new Class [] {Map.class}, (proxy, method, methodArgs) -> {if (method.getName (). Equals ("get ")) {retur 42;} annet {kast ny UnsupportedOperationException (" Ikke-støttet metode: "+ method.getName ());}});
Her definerte vi en handler som returnerer 42 for alle get-operasjoner og -kast Ikke-støttetOperationException for alt annet.
Det påberopes på nøyaktig samme måte:
(int) proxyInstance.get ("hei"); // 42 proxyInstance.put ("hei", "verden"); // unntak
5. Eksempel på timing dynamisk proxy
La oss undersøke et potensielt scenario for dynamiske fullmakter.
Anta at vi vil registrere hvor lang tid funksjonene våre tar å utføre. I denne grad definerer vi først en behandler som er i stand til å pakke inn det “virkelige” objektet, spore timinginformasjon og reflekterende påkalling:
offentlig klasse TimingDynamicInvocationHandler implementerer InvocationHandler {private static Logger LOGGER = LoggerFactory.getLogger (TimingDynamicInvocationHandler.class); private final Kartmetoder = 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) throws Throwable {long start = System.nanoTime (); Objektresultat = methods.get (method.getName ()). Påkalle (target, args); lang forløpt = System.nanoTime () - start; LOGGER.info ("Eksekvering {} ferdig i {} ns", method.getName (), forløpt); returresultat; }}
Deretter kan denne proxyen brukes på forskjellige objekttyper:
Map mapProxyInstance = (Map) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), new Class [] {Map.class}, new TimingDynamicInvocationHandler (new HashMap ())); mapProxyInstance.put ("hei", "verden"); CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), ny klasse [] {CharSequence.class}, ny TimingDynamicInvocationHandler ("Hello World")); csProxyInstance.length ()
Her har vi lagt et kart og en røyesekvens (String).
Påkallelser av proxy-metodene vil delegere til det innpakkede objektet, samt produsere loggingsuttalelser:
Utførelse satt ferdig i 19153 ns Utførelse ble ferdig i 8891 ns Utførelse charAt ferdig i 11152 ns Utførelseslengde ferdig i 10087 ns
6. Konklusjon
I denne raske opplæringen har vi undersøkt Java's dynamiske proxyer samt noen av dens mulige bruksområder.
Som alltid kan koden i eksemplene bli funnet på GitHub.