CodeGym /Java blog /Véletlen /Dinamikus proxy-k Java-ban
John Squirrels
Szint
San Francisco

Dinamikus proxy-k Java-ban

Megjelent a csoportban
Szia! Ma egy meglehetősen fontos és érdekes témát fogunk megvizsgálni: dinamikus proxy osztályok létrehozását Java nyelven. Nem túl egyszerű, ezért megpróbáljuk példákkal kitalálni :) Szóval, a legfontosabb kérdés: mik azok a dinamikus proxy-k és mire valók? A proxy osztály egyfajta "kiegészítés" az eredeti osztályon felül, amely lehetővé teszi, hogy szükség esetén módosítsuk az eredeti osztály viselkedését. Mit jelent a „viselkedés megváltoztatása”, és hogyan működik ez? Vegyünk egy egyszerű példát. Tegyük fel, hogy van egy Személy interfészünk és egy egyszerű Man osztályunk, amely megvalósítja ezt az interfészt

public interface Person {

   public void introduce(String name);
  
   public void sayAge(int age);
  
   public void sayWhereFrom(String city, String country);
}

public class Man implements Person {

   private String name;
   private int age;
   private String city;
   private String country;

   public Man(String name, int age, String city, String country) {
       this.name = name;
       this.age = age;
       this.city = city;
       this.country = country;
   }

   @Override
   public void introduce(String name) {

       System.out.println("My name is " + this.name);
   }

   @Override
   public void sayAge(int age) {
       System.out.println("I am " + this.age + " years old");
   }

   @Override
   public void sayWhereFrom(String city, String country) {

       System.out.println("I'm from " + this.city + ", " + this.country);
   }

   // ...getters, setters, etc.
}
A Man osztályunkban 3 módszer van: bevezet, mond kor és mond, honnan. Képzeljük el, hogy ezt az osztályt egy kész JAR-könyvtár részeként kaptuk, és nem tudjuk egyszerűen átírni a kódját. De a viselkedésén is változtatnunk kell. Például nem tudjuk, hogy melyik metódus hívható meg az objektumunkon, de azt szeretnénk, hogy a személyünk azt mondja: "Szia!" (senki sem szereti azt, aki udvariatlan), ha bármelyik módszert meghívják. Dinamikus proxyk - 2Mit tegyünk ebben a helyzetben? Szükségünk lesz néhány dologra:
  1. InvocationHandler

Mi ez? Az InvocationHandler egy speciális interfész, amely lehetővé teszi, hogy bármilyen metódushívást lefogjunk az objektumunkra, és hozzáadjuk a szükséges további viselkedést. Létre kell hoznunk saját elfogót, azaz egy osztályt, amely ezt a felületet megvalósítja. Ez elég egyszerű:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PersonInvocationHandler implements InvocationHandler {
  
private Person person;

public PersonInvocationHandler(Person person) {
   this.person = person;
}

 @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

       System.out.println("Hi!");
       return null;
   }
}
Csak egy interfész metódust kell megvalósítanunk: invoke() . És mellesleg azt teszi, amire szükségünk van: elfogja az objektumunkhoz intézett összes metódushívást, és hozzáadja a szükséges viselkedést (az invoke() metóduson belül a "Szia!"-t adjuk ki a konzolra.
  1. Az eredeti objektum és a proxyk.
Létrehozzuk az eredeti Man objektumunkat és egy "kiegészítőt" (proxyt) hozzá:

import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       // Create the original object
       Man arnold = new Man("Arnold", 30, "Thal", "Austria");

       // Get the class loader from the original object
       ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();

       // Get all the interfaces that the original object implements
       Class[] interfaces = arnold.getClass().getInterfaces();

       // Create a proxy for our arnold object
       Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

       // Call one of our original object's methods on the proxy object
       proxyArnold.introduce(arnold.getName());

   }
}
Ez nem tűnik túl egyszerűnek! Minden kódsorhoz külön megjegyzést tettem. Nézzük meg közelebbről, mi történik. Az első sorban egyszerűen elkészítjük az eredeti objektumot, amelyhez proxykokat hozunk létre. A következő két sor nehézséget okozhat:

 // Get the class loader from the original object
ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();

// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
Igazából itt semmi különös nem történik :) A negyedik sorban a speciális Proxy osztályt és annak statikus newProxyInstance() metódusát használjuk :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Ez a módszer csak létrehozza a proxy objektumunkat. A metódusnak átadjuk az eredeti osztályról az utolsó lépésben kapott információkat (annak ClassLoaderjét és interfészeinek listáját), valamint a korábban létrehozott InvocationHandler objektumot. A lényeg, hogy ne felejtsük el átadni az eredeti arnold tárgyunkat az invokációkezelőnek, különben nem lesz mit "kezelni" :) Mi lett a vége? Most van egy proxy objektumunk: proxyArnold . Meghívhatja a Személy interfész bármely metódust. Miért? Mert itt adtunk neki egy listát az összes interfészről:

// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Most már ismeri a Személy interfész összes metódusát. Ezenkívül átadtunk a proxynak egy PersonInvocationHandler objektumot, amely úgy van konfigurálva, hogy működjön együtt az arnold objektummal:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Ha most meghívjuk a személy interfész bármely metódusát a proxy objektumon, a kezelőnk elfogja a hívást, és helyette a saját invoke() metódusát hajtja végre. Próbáljuk meg futtatni a main() metódust! Konzol kimenet:

Hi!
Kiváló! Látjuk, hogy az eredeti Person.introduce() metódus helyett a PersonInvocationHandler() invoke() metódusa a neve:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hi!");
   return null;
}
"Szia!" jelenik meg a konzolon, de nem pontosan ezt a viselkedést szerettük volna :/ Azt próbáltuk elérni, hogy először a „Szia!” majd hívja meg magát az eredeti metódust.Más szóval a metódushívást

proxyArnold.introduce(arnold.getName());
"Szia! A nevem Arnold", nem pedig egyszerűen "Szia!" Hogyan érhetjük el ezt? Nem bonyolult: csak némi szabadságot kell vállalnunk a kezelőnkkel és az invoke() metódussal :) Figyeljünk arra, hogy milyen argumentumokat adunk át ehhez a metódushoz:

public Object invoke(Object proxy, Method method, Object[] args)
Az invoke() metódus hozzáfér az eredetileg meghívott metódushoz és annak összes argumentumához (Method metódus, Object[] args). Más szóval, ha a proxyArnold.introduce(arnold.getName()) metódust úgy hívjuk meg , hogy az invoke() metódus kerüljön meghívásra az insert() metódus helyett , akkor ezen a metóduson belül hozzáférünk az eredeti bevezetés() metódushoz . és az érve! Ennek eredményeként ezt tehetjük:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PersonInvocationHandler implements InvocationHandler {

   private Person person;

   public PersonInvocationHandler(Person person) {

       this.person = person;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       System.out.println("Hi!");
       return method.invoke(person, args);
   }
}
Most az invoke() metódusban hozzáadtunk egy hívást az eredeti metódushoz. Ha most megpróbáljuk futtatni az előző példánk kódját:

import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       // Create the original object
       Man arnold = new Man("Arnold", 30, "Thal", "Austria");

       // Get the class loader from the original object
       ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();

       // Get all the interfaces that the original object implements
       Class[] interfaces = arnold.getClass().getInterfaces();

       // Create a proxy for our arnold object
       Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

       // Call one of our original object's methods on the proxy object
       proxyArnold.introduce(arnold.getName());
   }
}
majd meglátjuk, hogy most már minden úgy működik, ahogy kell :) Konzol kimenet:

Hi! My name is Arnold
Mikor lehet erre szüksége? Valójában elég gyakran. A "dinamikus proxy" tervezési mintát aktívan használják a népszerű technológiákban... Ó, mellesleg elfelejtettem megemlíteni, hogy a Dynamic Proxy egy tervezési minta! Gratulálunk, tanultál még egyet! :) Dinamikus proxyk - 3Például aktívan használják a biztonsággal kapcsolatos népszerű technológiákban és keretrendszerekben. Képzelje el, hogy 20 metódusa van, amelyeket csak a programjába bejelentkezett felhasználók hajthatnak végre. Az elsajátított technikák segítségével egyszerűen hozzáadhat ehhez a 20 módszerhez egy ellenőrzést, hogy megbizonyosodjon arról, hogy a felhasználó érvényes hitelesítő adatokat adott-e meg anélkül, hogy az ellenőrző kódot az egyes módszerekben megkettőzné. Vagy tegyük fel, hogy szeretne létrehozni egy naplót, amelyben minden felhasználói művelet rögzítésre kerül. Ez proxy használatával is könnyen megtehető. Még most is egyszerűen hozzáadhat kódot a fenti példánkhoz, hogy a metódus neve megjelenjen az invoke() híváskor , és ez egy rendkívül egyszerű naplót készítene a programunkról :) Összegzésként figyeljen egy fontos korlátozásra. A proxy objektum interfészekkel működik, nem osztályokkal. Egy interfészhez proxy jön létre. Vessen egy pillantást erre a kódra:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Itt létrehozunk egy proxyt kifejezetten a Személyes felülethez. Ha megpróbálunk proxyt létrehozni az osztályhoz, azaz megváltoztatjuk a hivatkozás típusát, és megpróbálunk átküldeni a Man osztályba, az nem fog működni.

Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

proxyArnold.introduce(arnold.getName());
Kivétel a "main" szálban java.lang.ClassCastException: com.sun.proxy.$Proxy0 nem küldhető át az embernek. Az interfész megléte abszolút követelmény. A proxyk interfészekkel működnek. Mára ennyi :) Na, most jó lenne megoldani pár feladatot! :) A következő alkalomig!
Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION