CodeGym /Java Blog /Random-IT /Proxy dinamici in Java
John Squirrels
Livello 41
San Francisco

Proxy dinamici in Java

Pubblicato nel gruppo Random-IT
CIAO! Oggi considereremo un argomento piuttosto importante e interessante: la creazione di classi proxy dinamiche in Java. Non è molto semplice, quindi proveremo a capirlo usando esempi :) Quindi, la domanda più importante: cosa sono i proxy dinamici ea cosa servono? Una classe proxy è una sorta di "componente aggiuntivo" in cima alla classe originale, che ci consente di modificare il comportamento della classe originale se necessario. Cosa significa "cambiare comportamento" e come funziona? Considera un semplice esempio. Supponiamo di avere un'interfaccia Person e una semplice classe Man che implementa questa interfaccia

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.
}
La nostra classe Man ha 3 metodi: introduce, sayAge e sayWhereFrom. Immagina di avere questa classe come parte di una libreria JAR standard e non possiamo semplicemente riscriverne il codice. Ma dobbiamo anche cambiare il suo comportamento. Ad esempio, non sappiamo quale metodo potrebbe essere chiamato sul nostro oggetto, ma vogliamo che la nostra persona dica "Ciao!" (a nessuno piace qualcuno che è scortese) quando viene chiamato uno qualsiasi dei metodi. Proxy dinamici - 2Cosa dovremmo fare in questa situazione? Avremo bisogno di alcune cose:
  1. InvocazioneHandler

Cos'è questo? InvocationHandler è un'interfaccia speciale che ci consente di intercettare qualsiasi chiamata di metodo al nostro oggetto e aggiungere il comportamento aggiuntivo di cui abbiamo bisogno. Dobbiamo creare il nostro interceptor, cioè creare una classe che implementi questa interfaccia. Questo è piuttosto semplice:

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;
   }
}
Abbiamo bisogno di implementare un solo metodo di interfaccia: invoke() . E, a proposito, fa ciò di cui abbiamo bisogno: intercetta tutte le chiamate di metodo al nostro oggetto e aggiunge il comportamento necessario (all'interno del metodo invoke() , inviamo "Ciao!" alla console).
  1. L'oggetto originale e i relativi proxy.
Creiamo il nostro oggetto Man originale e un "add-on" (proxy) per esso:

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

   }
}
Questo non sembra molto semplice! Ho aggiunto specificamente un commento per ogni riga di codice. Diamo un'occhiata più da vicino a cosa sta succedendo. Nella prima riga creiamo semplicemente l'oggetto originale per il quale creeremo i proxy. Le seguenti due righe potrebbero causare difficoltà:

 // 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();
In realtà, non sta succedendo niente di veramente speciale qui :) Nella quarta riga, usiamo la speciale classe Proxy e il suo metodo statico newProxyInstance() :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Questo metodo crea solo il nostro oggetto proxy. Passiamo al metodo le informazioni sulla classe originale, che abbiamo ricevuto nell'ultimo passaggio (il suo ClassLoader e un elenco delle sue interfacce), così come l' oggetto InvocationHandler creato in precedenza. La cosa principale è non dimenticare di passare il nostro oggetto arnold originale al gestore di invocazione, altrimenti non ci sarà nulla da "gestire" :) Con cosa siamo finiti? Ora abbiamo un oggetto proxy: proxyArnold . Può chiamare qualsiasi metodo dell'interfaccia Person . Perché? Perché gli abbiamo fornito un elenco di tutte le interfacce qui:

// 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));
Ora conosce tutti i metodi dell'interfaccia Persona . Inoltre, abbiamo passato al nostro proxy un oggetto PersonInvocationHandler configurato per lavorare con l' oggetto arnold :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Ora, se chiamiamo qualsiasi metodo dell'interfaccia Person sull'oggetto proxy, il nostro gestore intercetta la chiamata ed esegue invece il proprio metodo invoke() . Proviamo ad eseguire il metodo main() ! Uscita console:

Hi!
Eccellente! Vediamo che invece del metodo Person.introduce() originale , viene chiamato il metodo invoke() del nostro PersonInvocationHandler() :

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

   System.out.println("Hi!");
   return null;
}
"CIAO!" viene visualizzato sulla console, ma questo non è esattamente il comportamento che volevamo :/ Quello che stavamo cercando di ottenere è visualizzare prima "Ciao!" e quindi chiamare il metodo originale stesso.In altre parole, la chiamata al metodo

proxyArnold.introduce(arnold.getName());
dovrebbe mostrare "Ciao! Mi chiamo Arnold", non semplicemente "Ciao!" Come possiamo raggiungere questo risultato? Non è complicato: dobbiamo solo prenderci delle libertà con il nostro gestore e il metodo invoke() :) Presta attenzione a quali argomenti vengono passati a questo metodo:

public Object invoke(Object proxy, Method method, Object[] args)
Il metodo invoke() ha accesso al metodo invocato originariamente ea tutti i suoi argomenti (Method method, Object[] args). In altre parole, se chiamiamo il metodo proxyArnold.introduce(arnold.getName()) in modo che venga chiamato il metodo invoke() invece del metodo introduce() , allora all'interno di questo metodo abbiamo accesso al metodo introduttore originale e il suo argomento! Di conseguenza, possiamo fare questo:

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);
   }
}
Ora nel metodo invoke() abbiamo aggiunto una chiamata al metodo originale. Se ora proviamo a eseguire il codice del nostro esempio precedente:

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());
   }
}
poi vedremo che ora tutto funziona come dovrebbe :) Output della console:

Hi! My name is Arnold
Quando potresti averne bisogno? In realtà, abbastanza spesso. Il modello di progettazione "proxy dinamico" viene utilizzato attivamente nelle tecnologie più diffuse. Oh, a proposito, ho dimenticato di menzionare che il proxy dinamico è un modello di progettazione! Congratulazioni, ne hai imparato un altro! :) Proxy dinamici - 3Ad esempio, viene utilizzato attivamente nelle tecnologie e nei framework più diffusi relativi alla sicurezza. Immagina di avere 20 metodi che dovrebbero essere eseguiti solo dagli utenti che hanno effettuato l'accesso al tuo programma. Utilizzando le tecniche che hai appreso, potresti facilmente aggiungere a questi 20 metodi un controllo per vedere se l'utente ha inserito credenziali valide senza duplicare il codice di verifica in ciascun metodo. Oppure supponi di voler creare un registro in cui verranno registrate tutte le azioni dell'utente. Questo è anche facile da fare usando un proxy. Anche ora, potresti semplicemente aggiungere codice al nostro esempio sopra in modo che il nome del metodo venga visualizzato quando chiami invoke() , e ciò produrrebbe un registro super semplice del nostro programma :) In conclusione, presta attenzione a un'importante limitazione. Un oggetto proxy funziona con le interfacce, non con le classi. Viene creato un proxy per un'interfaccia. Dai un'occhiata a questo codice:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Qui creiamo un proxy specifico per l' interfaccia Person . Se proviamo a creare un proxy per la classe, cioè cambiamo il tipo di riferimento e proviamo a lanciare alla classe Man , non funzionerà.

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

proxyArnold.introduce(arnold.getName());
Eccezione nel thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 non può essere trasmesso a Man Avere un'interfaccia è un requisito assoluto. I proxy funzionano con le interfacce. Per oggi è tutto :) Bene, ora sarebbe bello risolvere alcuni compiti! :) Fino alla prossima volta!
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION