JavaRush /Blog Java /Random-FR /Proxy dynamiques en Java

Proxy dynamiques en Java

Publié dans le groupe Random-FR
Bonjour! Aujourd'hui, nous examinerons un sujet assez important et intéressant : la création de classes proxy dynamiques en Java. Ce n'est pas trop simple, alors essayons de le comprendre avec des exemples :) Alors, la question la plus importante : que sont les proxys dynamiques et à quoi servent-ils ? Une classe proxy est une sorte de « superstructure » par rapport à la classe d'origine, qui nous permet de modifier son comportement si nécessaire. Que signifie changer de comportement et comment ça marche ? Regardons un exemple simple. Disons que nous avons une interface Personet une classe simple Manqui implémente cette interface

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

   //..геттеры, сеттеры, и т.д.
}
Notre cours Mancomporte 3 méthodes : présentez-vous, dites votre âge et dites d'où vous venez. Imaginons que nous ayons reçu cette classe dans le cadre d'une bibliothèque JAR prête à l'emploi et que nous ne puissions pas simplement prendre et réécrire son code. Cependant, nous devons changer son comportement. Par exemple, nous ne savons pas quelle méthode sera appelée sur notre objet, et nous voulons donc que la personne dise d'abord « Bonjour ! » lorsqu'elle appelle l'une d'entre elles. (personne n'aime quelqu'un qui est impoli). Proxy dynamiques - 1Comment devons-nous agir dans une telle situation ? Nous aurons besoin de quelques choses :
  1. InvocationHandler

Ce que c'est? Il peut être traduit littéralement par « intercepteur d’appel ». Cela décrit assez précisément son objectif. InvocationHandlerest une interface spéciale qui nous permet d'intercepter tous les appels de méthode à notre objet et d'ajouter le comportement supplémentaire dont nous avons besoin. Nous devons créer notre propre intercepteur, c'est-à-dire créer une classe et implémenter cette interface. C'est assez simple :

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;
   }
}
Nous devons implémenter une seule méthode d'interface - invoke(). En fait, il fait ce dont nous avons besoin - il intercepte tous les appels de méthode à notre objet et ajoute le comportement nécessaire (ici, nous invoke()imprimons « Bonjour ! » sur la console à l'intérieur de la méthode).
  1. L'objet d'origine et son proxy.
Créons un objet original Manet une « superstructure » (proxy) pour celui-ci :

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

   }
}
Cela n'a pas l'air très simple ! J’ai spécifiquement écrit un commentaire pour chaque ligne de code : regardons de plus près ce qui s’y passe.

Dans la première ligne, nous créons simplement l'objet original pour lequel nous allons créer un proxy. Les deux lignes suivantes peuvent prêter à confusion :

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

//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
Mais il n'y a vraiment rien de spécial ici :) Pour créer un proxy, nous avons besoin ClassLoaderdu (chargeur de classe) de l'objet d'origine et d'une liste de toutes les interfaces que notre classe d'origine (c'est-à-dire Man) implémente. Si vous ne savez pas de quoi il s'agit ClassLoader, vous pouvez lire cet article sur le chargement de classes dans la JVM ou celui-ci sur Habré , mais ne vous en préoccupez pas trop pour l'instant. N'oubliez pas que nous obtenons un peu d'informations supplémentaires dont nous aurons ensuite besoin pour créer l'objet proxy. Sur la quatrième ligne nous utilisons une classe spéciale Proxyet sa méthode statique newProxyInstance():

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Cette méthode crée simplement notre objet proxy. À la méthode, nous transmettons les informations sur la classe d'origine que nous avons reçues à l'étape précédente (elle ClassLoaderet la liste de ses interfaces), ainsi que l'objet de l'intercepteur que nous avons créé précédemment - InvocationHandler'a. L'essentiel est de ne pas oublier de passer notre objet d'origine à l'intercepteur vasia, sinon il n'aura rien à "intercepter" :) Qu'est-ce qu'on s'est retrouvé avec ? Nous avons maintenant un objet proxy vasiaProxy. Il peut appeler n'importe quelle méthode d'interfacePerson . Pourquoi? Parce que nous lui avons transmis une liste de toutes les interfaces - ici :

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

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Il connaît désormais toutes les méthodes d'interface Person. De plus, nous avons transmis à notre proxy un objet PersonInvocationHandlerconfiguré pour fonctionner avec l'objetvasia :

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Désormais, si nous appelons une méthode d'interface sur l'objet proxy Person, notre intercepteur « attrapera » cet appel et exécutera sa propre méthode à la place invoke(). Essayons d'exécuter la méthode main()! Sortie de la console :  Bonjour ! Super! On voit qu'au lieu de la vraie méthode, notre Person.introduce()méthode s'appelle : invoke()PersonInvocationHandler()

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

   System.out.println("Hello!");
   return null;
}
Et la console affichait « Bonjour ! » Mais ce n'est pas exactement le comportement que nous souhaitions obtenir :/ Selon notre idée, « Bonjour ! » devrait d'abord être affiché, puis la méthode elle-même que nous appelons devrait fonctionner. En d’autres termes, cette méthode appelle :

proxyVasia.introduce(vasia.getName());
devrait afficher sur la console « Bonjour ! Je m'appelle Vasya », et pas seulement « Bonjour ! Comment pouvons-nous y parvenir? Rien de compliqué : il suffit de bricoler un peu notre intercepteur et notre méthode invoke():) Faites attention aux arguments qui sont passés à cette méthode :

public Object invoke(Object proxy, Method method, Object[] args)
Une méthode invoke()a accès à la méthode qu’elle est appelée et à tous ses arguments (méthode Method, arguments Object[]). En d’autres termes, si nous appelons une méthode proxyVasia.introduce(vasia.getName()), et qu’au lieu d’une méthode , introduce()une méthode est appelée invoke(), à l’intérieur de cette méthode, nous avons accès à la fois à la méthode d’origine introduce()et à son argument ! En conséquence, nous pouvons faire quelque chose comme ceci :

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);
   }
}
Nous avons maintenant ajouté invoke()un appel à la méthode d'origine à la méthode. Si nous essayons maintenant d'exécuter le code de notre exemple précédent :

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());
   }
}
alors nous verrons que maintenant tout fonctionne comme il se doit :) Sortie de la console : Bonjour ! Je m'appelle Vasya. Où pourriez-vous en avoir besoin ? En fait, dans de nombreux endroits. Le design pattern « proxy dynamique » est activement utilisé dans les technologies populaires... et d'ailleurs, j'ai oublié de vous dire que Dynamic Proxyc'est un pattern ! Félicitations, vous en avez appris un autre ! :) Proxy dynamiques - 2Ainsi, il est activement utilisé dans les technologies et cadres populaires liés à la sécurité. Imaginez que vous disposez de 20 méthodes qui ne peuvent être exécutées que par les utilisateurs connectés à votre programme. En utilisant les techniques que vous avez apprises, vous pouvez facilement ajouter à ces 20 méthodes une vérification pour voir si l'utilisateur a saisi un identifiant et un mot de passe, sans dupliquer le code de vérification séparément dans chaque méthode. Ou, par exemple, si vous souhaitez créer un journal dans lequel toutes les actions des utilisateurs seront enregistrées, cela est également facile à faire en utilisant un proxy. Vous pouvez même maintenant : ajoutez simplement du code à l'exemple pour que le nom de la méthode soit affiché dans la console lorsqu'elle est appelée invoke(), et vous obtiendrez un simple journal de notre programme :) A la fin de la conférence, faites attention à un point important limitation . La création d'un objet proxy s'effectue au niveau de l'interface et non au niveau de la classe. Un proxy est créé pour l'interface. Jetez un œil à ce code :

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Ici, nous créons un proxy spécifiquement pour l'interface Person. Si nous essayons de créer un proxy pour la classe, c'est-à-dire que nous modifions le type de lien et essayons de le diffuser vers la classe Man, rien ne fonctionnera.

Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

proxyVasia.introduce(vasia.getName());
Exception dans le thread "main" java.lang.ClassCastException : com.sun.proxy.$Proxy0 ne peut pas être converti en Man La présence d'une interface est une condition obligatoire. Le proxy fonctionne au niveau de l'interface. C'est tout pour aujourd'hui :) Comme matériel supplémentaire sur le thème des proxys, je peux vous recommander une excellente vidéo ainsi qu'un bon article . Eh bien, maintenant, ce serait bien de résoudre quelques problèmes ! :) À bientôt!
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION