CodeGym /Blog Java /Aleatoriu /Proxy dinamici în Java
John Squirrels
Nivel
San Francisco

Proxy dinamici în Java

Publicat în grup
Bună! Astăzi vom lua în considerare un subiect destul de important și interesant: crearea de clase proxy dinamice în Java. Nu este foarte simplu, așa că vom încerca să ne dăm seama folosind exemple :) Deci, cea mai importantă întrebare: ce sunt proxy-urile dinamice și pentru ce sunt acestea? O clasă proxy este un fel de „supliment” deasupra clasei originale, care ne permite să schimbăm comportamentul clasei originale dacă este necesar. Ce înseamnă „schimbarea comportamentului” și cum funcționează? Luați în considerare un exemplu simplu. Să presupunem că avem o interfață Person și o clasă Man simplă care implementează această interfață

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.
}
Clasa noastră Man are 3 metode: introduce, say Age și sayWhereFrom. Imaginați-vă că am primit această clasă ca parte a unei biblioteci JAR de pe raft și nu putem pur și simplu să-i rescriem codul. Dar trebuie să-i schimbăm și comportamentul. De exemplu, nu știm ce metodă ar putea fi apelată pe obiectul nostru, dar vrem ca persoana noastră să spună „Bună!” (nimănui nu-i place pe cineva care este nepoliticos) când se apelează la oricare dintre metode. Proxy dinamici - 2Ce ar trebui să facem în această situație? Vom avea nevoie de câteva lucruri:
  1. InvocationHandler

Ce este asta? InvocationHandler este o interfață specială care ne permite să interceptăm orice apel de metodă la obiectul nostru și să adăugăm comportamentul suplimentar de care avem nevoie. Trebuie să ne creăm propriul interceptor, adică să creăm o clasă care implementează această interfață. Acesta este destul de simplu:

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;
   }
}
Trebuie să implementăm o singură metodă de interfață: invoke() . Și, apropo, face ceea ce avem nevoie: interceptează toate apelurile de metodă către obiectul nostru și adaugă comportamentul necesar (în cadrul metodei invoke() , scoatem „Hi!” în consolă).
  1. Obiectul original și proxy-urile acestuia.
Creăm obiectul Man original și un „supliment” (proxy) pentru acesta:

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());

   }
}
Acest lucru nu pare foarte simplu! Am adăugat în mod special un comentariu pentru fiecare linie de cod. Să aruncăm o privire mai atentă la ceea ce se întâmplă. În prima linie, facem pur și simplu obiectul original pentru care vom crea proxy. Următoarele două rânduri vă pot cauza dificultăți:

 // 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();
De fapt, nu se întâmplă nimic cu adevărat special aici :) În a patra linie, folosim clasa specială Proxy și metoda sa statică newProxyInstance() :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Această metodă creează doar obiectul nostru proxy. Trecem metodei informațiile despre clasa originală, pe care le-am primit în ultimul pas ( ClassLoader- ul său și o listă a interfețelor sale), precum și obiectul InvocationHandler creat anterior . Principalul lucru este să nu uităm să trecem obiectul nostru original arnold la handler-ul de invocare, altfel nu va mai fi nimic de „tratat” :) Cu ce ​​am ajuns? Acum avem un obiect proxy: proxyArnold . Poate apela orice metodă a interfeței Persoană . De ce? Pentru că i-am dat aici o listă cu toate interfețele:

// 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));
Acum știe despre toate metodele interfeței Persoană . În plus, am transmis proxy-ului nostru un obiect PersonInvocationHandler configurat să funcționeze cu obiectul arnold :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Acum, dacă apelăm orice metodă a interfeței Person pe obiectul proxy, handlerul nostru interceptează apelul și execută în schimb propria sa metodă invoke() . Să încercăm să rulăm metoda main() ! Ieșire din consolă:

Hi!
Excelent! Vedem că, în loc de metoda originală Person.introduce() , metoda invoke() a PersonInvocationHandler() se numește:

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

   System.out.println("Hi!");
   return null;
}
"Bună!" este afișat pe consolă, dar acesta nu este tocmai comportamentul pe care l-am dorit :/ Ceea ce încercam să obținem este să afișam mai întâi „Hi!” și apoi apelați metoda originală în sine.Cu alte cuvinte, apelul metodei

proxyArnold.introduce(arnold.getName());
ar trebui să afișeze „Hi! Numele meu este Arnold”, nu doar „Hi!” Cum putem realiza acest lucru? Nu este complicat: trebuie doar să ne luăm câteva libertăți cu handlerul nostru și cu metoda invoke() :) Fiți atenți la ce argumente sunt transmise acestei metode:

public Object invoke(Object proxy, Method method, Object[] args)
Metoda invoke() are acces la metoda invocată inițial și la toate argumentele acesteia (metoda Method, Object[] args). Cu alte cuvinte, dacă numim metoda proxyArnold.introduce(arnold.getName()) astfel încât metoda invoke() să fie apelată în locul metodei introduce() , atunci în această metodă avem acces la metoda introduce() originală si argumentul ei! Ca urmare, putem face acest lucru:

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);
   }
}
Acum, în metoda invoke() am adăugat un apel la metoda originală. Dacă încercăm acum să rulăm codul din exemplul nostru anterior:

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());
   }
}
apoi vom vedea că acum totul funcționează așa cum ar trebui :) Ieșire consolă:

Hi! My name is Arnold
Când ai putea avea nevoie de asta? De fapt, destul de des. Modelul de design „dynamic proxy” este folosit activ în tehnologiile populare... A, apropo, am uitat să menționez că Dynamic Proxy este un model de design! Felicitări, ai mai învățat una! :) Proxy dinamici - 3De exemplu, este utilizat în mod activ în tehnologii și cadre populare legate de securitate. Imaginați-vă că aveți 20 de metode care ar trebui să fie executate numai de utilizatorii care sunt conectați la programul dvs. Folosind tehnicile pe care le-ați învățat, puteți adăuga cu ușurință acestor 20 de metode o verificare pentru a vedea dacă utilizatorul a introdus acreditări valide fără a duplica codul de verificare în fiecare metodă. Sau să presupunem că doriți să creați un jurnal în care vor fi înregistrate toate acțiunile utilizatorului. Acest lucru este, de asemenea, ușor de făcut folosind un proxy. Chiar și acum, puteți pur și simplu adăuga cod la exemplul nostru de mai sus, astfel încât numele metodei să fie afișat atunci când apelați invoke() și asta ar produce un jurnal super simplu al programului nostru :) În concluzie, acordați atenție unei limitări importante. Un obiect proxy funcționează cu interfețe, nu cu clase. Este creat un proxy pentru o interfață. Aruncă o privire la acest cod:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Aici creăm un proxy special pentru interfața Persoană . Dacă încercăm să creăm un proxy pentru clasă, adică să schimbăm tipul de referință și să încercăm să trimitem la clasa Man , nu va funcționa.

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

proxyArnold.introduce(arnold.getName());
Excepție în firul „principal” java.lang.ClassCastException: com.sun.proxy.$Proxy0 nu poate fi transmis către Man A avea o interfață este o cerință absolută. Proxy-urile funcționează cu interfețe. Atât pentru azi :) Ei bine, acum ar fi bine să rezolvăm câteva sarcini! :) Pana data viitoare!
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION