JavaRush /Java Blog /Random-IT /Proxy dinamici in Java

Proxy dinamici in Java

Pubblicato nel gruppo Random-IT
Ciao! Oggi esamineremo un argomento piuttosto importante e interessante: la creazione di classi proxy dinamiche in Java. Non è troppo semplice, quindi proviamo a capirlo con degli esempi :) Quindi, la domanda più importante: cosa sono i proxy dinamici e a cosa servono? Una classe proxy è una sorta di “sovrastruttura” sulla classe originale, che ci consente di modificarne il comportamento se necessario. Cosa significa cambiare comportamento e come funziona? Diamo un'occhiata a un semplice esempio. Diciamo di avere un'interfaccia Persone una classe semplice Manche implementa questa interfaccia
public interface Person {

   public void introduce(String name);

   public void sayAge(int age);

   public void sayFrom(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("Меня зовут " + this.name);
   }

   @Override
   public void sayAge(int age) {
       System.out.println("Мне " + this.age + " years");
   }

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

       System.out.println("Я из города " + this.city + ", " + this.country);
   }

   //..геттеры, сеттеры, и т.д.
}
La nostra lezione Manha 3 metodi: presentati, dì la tua età e di' da dove vieni. Immaginiamo di aver ricevuto questa classe come parte di una libreria JAR già pronta e di non poter semplicemente prendere e riscrivere il suo codice. Dobbiamo però cambiare il suo comportamento. Ad esempio, non sappiamo quale metodo verrà chiamato sul nostro oggetto e quindi vogliamo che la persona dica prima "Ciao!" quando lo chiama. (a nessuno piace qualcuno che è scortese). Procure dinamiche - 1Cosa dovremmo fare in una situazione del genere? Avremo bisogno di alcune cose:
  1. InvocationHandler

Cos'è? Può essere letteralmente tradotto come “intercettatore di chiamata”. Questo descrive il suo scopo in modo abbastanza accurato. 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, ovvero creare una classe e implementare questa interfaccia. È 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("Hello!");
       return null;
   }
}
Dobbiamo implementare un solo metodo di interfaccia: invoke(). In effetti, fa ciò di cui abbiamo bisogno: intercetta tutte le chiamate di metodo al nostro oggetto e aggiunge il comportamento necessario (qui stampiamo invoke()"Hello!" sulla console all'interno del metodo).
  1. L'oggetto originale e il suo proxy.
Creiamo un oggetto originale Mane una "sovrastruttura" (proxy):
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       //Создаем оригинальный an object
       Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");

       //Получаем загрузчик класса у оригинального an object
       ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

       //Получаем все интерфейсы, которые реализует оригинальный an object
       Class[] interfaces = vasia.getClass().getInterfaces();

       //Создаем прокси нашего an object vasia
       Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

       //Вызываем у прокси an object один из методов нашего оригинального an object
       proxyVasia.introduce(vasia.getName());

   }
}
Non sembra molto semplice! Ho scritto appositamente un commento per ogni riga di codice: diamo un’occhiata più da vicino a cosa sta succedendo lì.

Nella prima riga creiamo semplicemente l'oggetto originale per il quale creeremo un proxy. Le due righe seguenti potrebbero creare confusione:
//Получаем загрузчик класса у оригинального an object
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
Ma non c'è davvero niente di speciale qui :) Per creare un proxy, abbiamo bisogno ClassLoaderdel (caricatore di classi) dell'oggetto originale e di un elenco di tutte le interfacce Manimplementate dalla nostra classe originale (cioè ). Se non sai di cosa si tratta ClassLoader, puoi leggere questo articolo sul caricamento delle classi nella JVM o questo su Habré , ma non preoccuparti troppo ancora. Ricorda solo che stiamo ottenendo alcune informazioni extra di cui avremo bisogno per creare l'oggetto proxy. Nella quarta riga utilizziamo una classe speciale Proxye il suo metodo statico newProxyInstance():
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Questo metodo crea semplicemente il nostro oggetto proxy. Al metodo passiamo le informazioni sulla classe originale che abbiamo ricevuto nel passaggio precedente (essa ClassLoadere l'elenco delle sue interfacce), nonché l'oggetto dell'interceptor che abbiamo creato in precedenza - InvocationHandler'a. La cosa principale è non dimenticare di passare il nostro oggetto originale all'interceptor vasia, altrimenti non avrà nulla da "intercettare" :) Cosa abbiamo ottenuto alla fine? Ora abbiamo un oggetto proxy vasiaProxy. Può chiamare qualsiasi metodo di interfacciaPerson . Perché? Perché gli abbiamo passato un elenco di tutte le interfacce - qui:
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Ora è "a conoscenza" di tutti i metodi di interfaccia Person. Inoltre, abbiamo passato al nostro proxy un oggetto PersonInvocationHandlerconfigurato per funzionare con l'oggetto vasia:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Ora, se chiamiamo qualsiasi metodo di interfaccia sull'oggetto proxy Person, il nostro interceptor "catturarà" questa chiamata ed eseguirà invece il proprio metodo invoke(). Proviamo a eseguire il metodo main()! Output della console: Ciao! Grande! Vediamo che invece del metodo vero e proprio, il nostro Person.introduce()metodo si chiama : invoke()PersonInvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hello!");
   return null;
}
E la console mostrava "Ciao!" Ma questo non è esattamente il comportamento che volevamo ottenere :/ Secondo la nostra idea, prima dovrebbe essere visualizzato "Hello!" e poi il metodo stesso che stiamo chiamando dovrebbe funzionare. In altre parole, questo metodo chiama:
proxyVasia.introduce(vasia.getName());
dovrebbe restituire alla console "Ciao! Mi chiamo Vasya", e non solo "Ciao!" Come possiamo raggiungere questo risultato? Niente di complicato: devi solo armeggiare un po' con il nostro interceptor e il nostro metodo invoke():) Presta attenzione a quali argomenti vengono passati a questo metodo:
public Object invoke(Object proxy, Method method, Object[] args)
Un metodo invoke()ha invece accesso al metodo che viene chiamato e a tutti i suoi argomenti (metodo Method, Object[] args). In altre parole, se chiamiamo un metodo proxyVasia.introduce(vasia.getName())e invece di un metodo introduce()viene chiamato un metodo invoke(), all'interno di quel metodo avremo accesso sia al metodo originale introduce()che al suo argomento! Di conseguenza, possiamo fare qualcosa del genere:
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("Hello!");
       return method.invoke(person, args);
   }
}
Ora abbiamo aggiunto invoke()al metodo 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) {

       //Создаем оригинальный an object
       Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");

       //Получаем загрузчик класса у оригинального an object
       ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

       //Получаем все интерфейсы, которые реализует оригинальный an object
       Class[] interfaces = vasia.getClass().getInterfaces();

       //Создаем прокси нашего an object vasia
       Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

       //Вызываем у прокси an object один из методов нашего оригинального an object
       proxyVasia.introduce(vasia.getName());
   }
}
poi vedremo che ora tutto funziona come dovrebbe :) Console output: Ciao! Mi chiamo Vasya, dove potresti averne bisogno? In effetti, molti posti. Il modello di progettazione "proxy dinamico" è utilizzato attivamente nelle tecnologie più diffuse... e tra l'altro, ho dimenticato di dirti che Dynamic Proxyè un modello ! Congratulazioni, ne hai imparato un'altro! :) Procure dinamiche - 2Quindi, viene utilizzato attivamente nelle tecnologie e nei framework più diffusi relativi alla sicurezza. Immagina di avere 20 metodi che possono essere eseguiti solo dagli utenti registrati del tuo programma. Utilizzando le tecniche che hai imparato, puoi facilmente aggiungere a questi 20 metodi un controllo per vedere se l'utente ha inserito login e password, senza duplicare il codice di verifica separatamente in ciascun metodo. Oppure, ad esempio, se desideri creare un registro in cui verranno registrate tutte le azioni dell'utente, anche questo è facile da fare utilizzando un proxy. Puoi anche adesso: aggiungi semplicemente il codice all'esempio in modo che il nome del metodo venga visualizzato nella console quando viene chiamato invoke()e otterrai un semplice registro del nostro programma :) Alla fine della lezione, presta attenzione a un aspetto importante limitazione . La creazione di un oggetto proxy avviene a livello di interfaccia, non a livello di classe. Viene creato un proxy per l'interfaccia. Dai un'occhiata a questo codice:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Qui creiamo un proxy specifico per l'interfaccia Person. Se proviamo a creare un proxy per la classe, cioè cambiamo il tipo di collegamento e proviamo a eseguire il cast alla classe Man, non funzionerà nulla.
Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

proxyVasia.introduce(vasia.getName());
Eccezione nel thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 non può essere trasmesso a Man La presenza di un'interfaccia è un requisito obbligatorio. Il proxy funziona a livello di interfaccia. Per oggi è tutto :) Come materiale aggiuntivo sul tema dei proxy posso consigliarvi un ottimo video e anche un buon articolo . Bene, ora sarebbe carino risolvere qualche problema! :) Ci vediamo!
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION