Dinamički proxyji u Javi

1. Uvod

Ovaj je članak o Javinim dinamičkim proxyjima - koji je jedan od primarnih proxy mehanizama koji su nam dostupni u jeziku.

Jednostavno rečeno, proxyji su fronte ili omoti koji pozivanje funkcije prolaze kroz vlastite uređaje (obično na stvarne metode) - potencijalno dodajući neke funkcionalnosti.

Dinamički proxyji omogućuju jednoj klasi s jednom metodom da servisira više poziva metoda proizvoljnim klasama s proizvoljnim brojem metoda. Dinamički proxy može se smatrati nekom vrstom Fasada, ali onaj koji se može pretvarati da je implementacija bilo kojeg sučelja. Ispod naslovnice, usmjerava sve pozive metode na jedan rukovatelj - the prizvati () metoda.

Iako to nije alat namijenjen svakodnevnim programskim zadacima, dinamički proxyji mogu biti vrlo korisni za pisce okvira. Može se koristiti i u onim slučajevima kada konkretne implementacije klase neće biti poznate do vremena izvođenja.

Ova je značajka ugrađena u standardni JDK, stoga nisu potrebne dodatne ovisnosti.

2. Rukovatelj pozivima

Izgradimo jednostavni proxy koji zapravo ne radi ništa osim ispisivanja metode za koju je zatraženo da se pozove i vratimo kodirani broj.

Prvo, moramo stvoriti podvrstu java.lang.reflect.InvocationHandler:

javna klasa DynamicInvocationHandler implementira InvocationHandler {private static Logger LOGGER = LoggerFactory.getLogger (DynamicInvocationHandler.class); @Override public Object invoke (Object proxy, Method method, Object [] args) baca Throwable {LOGGER.info ("Pozvana metoda: {}", method.getName ()); povratak 42; }}

Ovdje smo definirali jednostavan proxy koji bilježi koja je metoda pozvana i vraća 42.

3. Stvaranje instance proxyja

Proxy instanca koju servisira obrađivač poziva, koju smo upravo definirali, kreira se putem tvorničke metode poziva na java.lang.reflect.Proxy razred:

Map proxyInstance = (Map) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), nova klasa [] {Map.class}, novi DynamicInvocationHandler ());

Jednom kada imamo instancu proxyja, možemo se normalno pozvati na metode sučelja:

proxyInstance.put ("zdravo", "svijet");

Očekivano poruka o staviti() metoda koja se poziva ispisuje se u datoteci dnevnika.

4. Rukovatelj pozivima putem Lambda izraza

Od InvocationHandler je funkcionalno sučelje, moguće je definirati rukovatelj u redu pomoću lambda izraza:

Karta proxyInstance = (Map) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), nova klasa [] {Map.class}, (proxy, method, methodArgs) -> {if (method.getName (). Jednako ("get ")) {return 42;} else {bacanje novog UnsupportedOperationException (" Nepodržana metoda: "+ method.getName ());}});

Ovdje smo definirali obrađivač koji vraća 42 za sve operacije dobivanja i bacanja UnsupportedOperationException za sve ostalo.

Poziva se na potpuno isti način:

(int) proxyInstance.get ("zdravo"); // 42 proxyInstance.put ("zdravo", "svijet"); // iznimka

5. Primjer dinamičkog proxyja vremena

Ispitajmo jedan potencijalni scenarij iz stvarnog svijeta za dinamičke proxyje.

Pretpostavimo da želimo zabilježiti koliko vremena trebaju naše funkcije da se izvrše. U tu mjeru prvo definiramo rukovatelj sposoban za umotavanje "stvarnog" objekta, praćenje podataka o vremenu i reflektirajuće pozivanje:

javna klasa TimingDynamicInvocationHandler implementira InvocationHandler {private static Logger LOGGER = LoggerFactory.getLogger (TimingDynamicInvocationHandler.class); privatne konačne metode karte = novi HashMap (); cilj privatnog objekta; javni TimingDynamicInvocationHandler (Cilj objekta) {this.target = target; za (Metoda metode: target.getClass (). getDeclaredMethods ()) {this.methods.put (method.getName (), metoda); }} @Override javni Priziv objekta (objekt proxy, metoda metoda, objekt [] args) baca mogućnost bacanja {long start = System.nanoTime (); Rezultat objekta = methods.get (method.getName ()). Invoke (target, args); dugo prošlo = System.nanoTime () - start; LOGGER.info ("Izvršenje {} završeno za {} ns", method.getName (), prošlo); povratni rezultat; }}

Nakon toga, ovaj se proxy može koristiti na različitim vrstama objekata:

Map mapProxyInstance = (Map) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), nova klasa [] {Map.class}, novi TimingDynamicInvocationHandler (novi HashMap ())); mapProxyInstance.put ("zdravo", "svijet"); CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), nova klasa [] {CharSequence.class}, novi TimingDynamicInvocationHandler ("Pozdrav svijetu")); csProxyInstance.length ()

Ovdje smo proksirali kartu i niz znakova (String).

Pozivi proxy metoda dodijelit će se omotanom objektu, kao i izraditi zapisnike zapisnika:

Izvršenje završeno u 19153 ns Izvršenje završeno u 8891 ns Izvršno charAt završeno u 11152 ns Izvršenje duljina završeno u 10087 ns

6. Zaključak

U ovom smo brzom vodiču ispitali Java-ove dinamičke proxyje kao i neke od njegovih mogućih upotreba.

Kao i uvijek, kod u primjerima možete pronaći na GitHubu.