Java Reflection – Proxys dynamiques

En utilisant Java Reflection, vous créez des implémentations dynamiques d’interfaces au moment de l’exécution. Vous le faites en utilisant la classe java.lang.reflect.Proxy. Le nom de cette classe est la raison pour laquelle je fais référence à ces implémentations d’interface dynamique en tant que proxies dynamiques. Les proxys dynamiques peuvent être utilisés à de nombreuses fins différentes, par exemple la connexion à la base de données et la gestion des transactions, les objets simulés dynamiques pour les tests unitaires et d’autres méthodes d’interception de type AOP.

Création de Proxys

Vous créez des proxys dynamiques à l’aide de la méthode Proxy.newProxyInstance(). Les méthodes newProxyInstance() prennent 3 paramètres:

  1. Le ClassLoader qui consiste à “charger” la classe de proxy dynamique.
  2. Un tableau d’interfaces à implémenter.
  3. Et InvocationHandler pour transférer tous les appels de méthodes sur le proxy.

Voici un exemple :

Après avoir exécuté ce code, la variable proxy contient une implémentation dynamique de l’interface MyInterface. Tous les appels au proxy seront transférés vers l’implémentation handler de l’interface générale InvocationHandler. Les InvocationHandler sont couverts dans la section suivante.

Invocationhandler’s

Comme mentionné précédemment, vous devez passer une implémentation InvocationHandler à la méthode Proxy.newProxyInstance(). Tous les appels de méthode au proxy dynamique sont transférés à cette implémentation InvocationHandler. Voici à quoi ressemble l’interface InvocationHandler:

public interface InvocationHandler{ Object invoke(Object proxy, Method method, Object args) throws Throwable;}

Voici un exemple d’implémentation:

public class MyInvocationHandler implements InvocationHandler{ public Object invoke(Object proxy, Method method, Object args) throws Throwable { //do something "dynamic" }}

Le paramètre proxy passé à la méthode invoke() est l’objet proxy dynamique implémentant l’interface. Le plus souvent, vous n’avez pas besoin de cet objet.

L’objet Method passé dans la méthode invoke() représente la méthode appelée sur l’interface que le proxy dynamique implémente. À partir de l’objet Method, vous pouvez obtenir le nom de la méthode, les types de paramètres, le type de retour, etc. Voir le texte sur les méthodes pour plus d’informations.

Le tableau Object args contient les valeurs de paramètres transmises au proxy lorsque la méthode de l’interface implémentée a été appelée. Remarque : Les primitives (int, long, etc.) dans l’interface implémentée sont enveloppées dans leurs homologues d’objet (Entier, Long, etc.).

Cas d’utilisation connus

Les proxys dynamiques sont connus pour être utilisés au moins aux fins suivantes:

  • Connexion à la base de données et Gestion des transactions
  • Objets Simulés dynamiques pour les tests unitaires
  • Adaptation du conteneur DI aux interfaces d’usine personnalisées
  • Interception de méthode de type AOP

Connexion à la base de données et Gestion des transactions

Le framework Spring dispose d’un proxy de transaction qui peut démarrer et valider/annuler une transaction pour vous. Comment cela fonctionne est décrit plus en détail dans le texte Connexion avancée et Démarcation et Propagation des transactions, je ne le décrirai donc que brièvement. La séquence d’appel devient quelque chose le long de ceci :

Objets Simulés dynamiques pour les tests unitaires

Les outils de test Butterfly utilisent des proxies dynamiques pour implémenter des stubs, des mocks et des proxies dynamiques pour les tests unitaires. Lors du test d’une classe A qui utilise une autre classe B (interface vraiment), vous pouvez passer une implémentation fictive de B dans A au lieu d’un vrai B. Tous les appels de méthode sur B sont maintenant enregistrés et vous pouvez définir les valeurs de retour que le faux B doit renvoyer.

De plus, les outils de test Butterfly vous permettent d’envelopper un vrai B dans un faux B, de sorte que tous les appels de méthode sur le faux sont enregistrés, puis transmis au réel B. Cela permet de vérifier quelles méthodes ont été appelées sur un vrai B fonctionnel. Par exemple, si vous testez un DAO, vous pouvez envelopper la connexion à la base de données dans un faux. Le DAO ne verra pas la différence, et le DAO peut lire / écrire des données dans la base de données comme d’habitude puisque le simulacre transmet tous les appels à la base de données. Mais maintenant, vous pouvez vérifier via la maquette si le DAO utilise correctement la connexion, par exemple si le connection.close() est appelé (ou NON appelé), si vous vous y attendiez. Ce n’est normalement pas possible de déterminer à partir de la valeur de retour d’un DAO.

Adaptation du conteneur DI aux interfaces d’usine personnalisées

Le conteneur d’injection de dépendance Le conteneur papillon possède une fonctionnalité puissante qui vous permet d’injecter l’ensemble du conteneur dans les haricots produits par celui-ci. Mais, comme vous ne voulez pas de dépendance à l’interface du conteneur, le conteneur est capable de s’adapter à une interface d’usine personnalisée de votre conception. Vous n’avez besoin que de l’interface. Aucune mise en œuvre. Ainsi, l’interface d’usine et votre classe pourraient ressembler à ceci:

public interface IMyFactory { Bean bean1(); Person person(); ...}

Lorsque la classe MyAction appelle des méthodes sur l’instance IMyFactory injectée dans son constructeur par le conteneur, les appels de méthode sont traduits en appels à la méthode IContainer.instance(), qui est la méthode que vous utilisez pour obtenir des instances à partir du conteneur. De cette façon, un objet peut utiliser le conteneur papillon comme usine au moment de l’exécution, plutôt que d’avoir uniquement des dépendances injectées en lui-même au moment de la création. Et cela sans avoir de dépendances sur des interfaces spécifiques au conteneur Papillon.

Interception de méthode de type AOP

Le framework Spring permet d’intercepter les appels de méthode vers un bean donné, à condition que bean implémente une interface. Le framework Spring enveloppe le bean dans un proxy dynamique. Tous les appels au bean sont ensuite interceptés par le proxy. Le proxy peut décider d’appeler d’autres méthodes sur d’autres objets avant, au lieu de ou après avoir délégué l’appel de méthode au bean encapsulé.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.