Exemple : utilisation de classes proxy pour un DAO générique. Ne vous inquiétez pas ! : Java pour le plaisir : merveilleux serveur proxy en Java

Chaque fois que j'ai besoin d'espionner deux programmes communiquant via le protocole TCP/IP, j'utilise ce proxy.

/* * Serveur proxy * Copyright (C) 2003, 2004 Healy Computer Systems * www.intermediary.com * * Ce programme est un logiciel gratuit ; vous pouvez le redistribuer et/ou le modifier * selon les termes de la licence publique générale GNU telle que publiée par * la Free Software Foundation ; soit la version 2 de la Licence, soit * (à votre choix) toute version ultérieure.
* *Ce programme est distribué dans l'espoir qu'il sera utile, * mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de * QUALITÉ MARCHANDE ou D'ADAPTATION À UN USAGE PARTICULIER. Voir la * Licence publique générale GNU pour plus de détails.

* * Vous devriez avoir reçu une copie de la licence publique générale GNU * avec ce programme ; sinon, écrivez à Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.intermediary.socketproxy.handler ; importer com.intermediary.socketproxy.IDataHandler ; /** * Classe exemple/utilitaire qui implémente IDataHandler.
# port sur lequel le serveur proxy doit écouter # ce port écoutera le proxy, c'est-à-dire tout ce qui arrive sur ce port de cet ordinateur est envoyé au serveur proxy pour traitement. Listen_port=7777 # destination de transfert # ce à quoi le proxy répondra sera envoyé à cet hôte (ce que nous renvoyons en pushBytes) destination_host=localhost # port de destination # et c'est l'hôte de la destination destination_port=8787 # classe java pour traiter les données la saisie du serveur proxy # doit être disponible dans votre chemin de classe au démarrage du serveur proxy # ce gestionnaire traitera les messages entrant dans le proxy in_class=com.intermediary.socketproxy.handler.DataPassThrough # classe java pour traiter les données quittant le serveur proxy # doit être disponible dans votre chemin de classe au démarrage du serveur proxy # ce gestionnaire traitera les messages provenant du proxy out_class=com.intermediary.socketproxy.handler.DataPassThrough # section de journalisation # journal - configuration générale # taille maximale du fichier journal max_log_file_bytes=10000000 # nombre maximum de fichiers journaux. S'il y a plus de fichiers dans le dossier \logs que ceux affichés ici, lorsque le proxy est redémarré, le journal le plus ancien sera supprimé. log_file_count=5 # log - informations système # où les fichiers journaux sont stockés et comment ils sont nommés system_log_file=../log/log%g_system.txt # quel niveau de messages de journal seront écrits dans le fichier # acceptable : OFF, SEVERE , AVERTISSEMENT, INFO , CONFIG, FINE, FINER, FINEST, ALL system_log_level=ALL # quel niveau de messages de journal sera affiché à l'écran # autorisé : OFF, SEVERE, ATTENTION, INFO, CONFIG, FINE, FINER, FINEST, ALL system_console_level =INFO # journal - données entrantes # les données entrantes sont enregistrées ici inbound_log_file=../log/log%g_inbound.txt inbound_log_level=ALL # journal - données sortantes # les données sortantes sont enregistrées ici outbound_log_file=../log/log%g_outbound. txt outbound_log_level=TOUS
Commencé par appeler la méthode principale de la classe
com.intermediary.socketproxy.ProxyServer
Vous pouvez transmettre le chemin d'accès au fichier de configuration en tant qu'arguments. Si vous ne transférez pas, le fichier sera recherché à la racine.

Théorie et pratique Java

Décorer avec un proxy dynamique

Proxy dynamiques - un outil pratique pour les décorateurs et les adaptateurs de construction

Série de contenu :

Les proxys dynamiques fournissent un mécanisme dynamique supplémentaire pour la mise en œuvre de nombreux modèles de conception, notamment Façade, Pont, Intercepteur, Décorateur, Proxy, y compris les proxys distants et virtuels, et les modèles Adaptateur. Bien que tous ces modèles puissent facilement être implémentés à l'aide de classes normales au lieu de proxys dynamiques, dans de nombreux cas, la technologie d'utilisation d'un proxy dynamique est plus pratique et plus compacte et peut éliminer le besoin de créer et de générer manuellement de nombreuses classes.

Modèle proxy

Le modèle proxy implique la création d'un objet « stub » ou « de substitution » dont le but est d'accepter les demandes et de les transmettre à un autre objet qui effectue réellement le travail. Le modèle proxy est utilisé par invocation de méthode à distance (technologie RMI) pour faire apparaître un objet exécuté dans une autre JVM comme un objet local ; utilisez Enterprise JavaBeans (EJB) pour ajouter des limites d'appels, de sécurité et de transactions à distance ; utiliser les services Web JAX-RPC pour représenter les services distants en tant qu'objets locaux. Dans chaque cas, le comportement d'un objet distant potentiel est défini par une interface qui, de par sa nature, permet de multiples implémentations. Généralement, l'appelant ne peut pas savoir qu'ils contiennent une référence à un stub plutôt qu'à un objet réel, car ils implémentent tous deux la même interface ; Le stub supervise le travail de recherche des objets réels, de rassemblement des paramètres, de leur envoi à l'objet réel, de décompression de la valeur de sortie et de son impression sur le programme appelant. Les proxys peuvent être utilisés pour activer la communication à distance (comme dans RMI, EJB et JAX-RPC), empaqueter des objets à l'aide de politiques de sécurité (EJB), fournir un chargement paresseux d'objets coûteux (EJB Entity Beans) ou ajouter des instruments tels que la journalisation.

Dans les versions du JDK antérieures à 5.0, les stubs RMI (et leurs antipodes, les squelettes) étaient des classes générées au moment de la compilation par le compilateur RMI (rmic), qui fait partie de la boîte à outils du JDK. Pour chaque interface distante, une classe stub (proxy) est générée pour représenter l'objet distant. Il génère également un objet "squelette" qui fait le contraire du travail du stub dans la JVM distante : il désorganise les paramètres et appelle l'objet réel. De même, les outils JAX-RPC pour les services Web génèrent des classes proxy pour les services Web distants qui les exposent en tant qu'objets locaux.

Que les classes stub générées soient créées sous forme de code source ou de bytecode, la génération de code ajoute des étapes supplémentaires au processus de compilation et peut prêter à confusion en raison de la prolifération de classes portant des noms similaires. D'un autre côté, le mécanisme de proxy dynamique vous permet de créer un objet proxy au moment de l'exécution sans générer de classes stub au moment de la compilation. Dans JDK 5.0 et versions ultérieures, RMI utilise des proxys dynamiques au lieu des stubs générés, ce qui rend RMI plus facile à utiliser. De nombreux conteneurs J2EE utilisent également des proxys dynamiques pour implémenter les EJB. La technologie EJB s'appuie largement sur l'utilisation de l'interception pour mettre en œuvre la sécurité et établir les limites des transactions ; les proxys dynamiques simplifient la mise en œuvre de l'interception en fournissant un chemin de flux de commandes central pour toutes les méthodes appelées sur une interface.

Mécanisme de proxy dynamique

Au cœur d'un proxy dynamique se trouve l'interface InvocationHandler, présentée dans le listing 1. Le travail du gestionnaire d'appel consiste à exécuter réellement la méthode demandée au nom du proxy dynamique. Le gestionnaire d'appels est transmis à un objet Method (du package java.lang.reflect) et à une liste de paramètres qui doivent être transmis à la méthode ; dans le cas le plus simple, il peut simplement appeler la méthode réflexive Method.invoke() et renvoyer le résultat.

Listing 1. Interface InvocationHandler
interface publique InvocationHandler ( Object Invocation (Object Proxy, Method Method, Object args) throws Throwable; )

Chaque proxy est associé à un gestionnaire d'appels, qui est appelé à chaque fois qu'une des méthodes du proxy est appelée. Conformément au principe de conception selon lequel les interfaces définissent les types et les classes définissent les implémentations, les objets proxy peuvent implémenter une ou plusieurs interfaces, mais pas les classes. Étant donné que les classes proxy n'ont pas de noms accessibles, elles ne peuvent pas avoir de constructeurs et doivent donc être créées à l'aide d'usines. Le listing 2 montre l'implémentation la plus simple possible d'un proxy dynamique, qui implémente l'interface Set et envoie toutes les méthodes Set (ainsi que toutes les méthodes Object) à une instance Set encapsulée.

.
Listing 2. Ensemble d'encapsulation de proxy dynamique simple)

public class SetProxyFactory ( public static Set getSetProxy(final Set s) ( return (Set) Proxy.newProxyInstance (s.getClass().getClassLoader(), new Class ( Set.class), new InvocationHandler() ( public Object Ensure(Object proxy, Méthode méthode, Objet args) lance Throwable ( return method.invoke(s, args); ) ) ));

La classe SetProxyFactory contient une méthode de fabrique statique, getSetProxy(), qui renvoie un proxy dynamique qui implémente Set. L'objet proxy implémente Set - l'appelant ne peut pas déterminer (sauf par réflexion) si l'objet renvoyé est un proxy dynamique. Le proxy renvoyé par SetProxyFactory ne fait rien d'autre que distribuer une méthode à l'instance Set transmise à la méthode factory. Bien que le code de réflexion soit souvent difficile à lire, il n'est pas difficile de suivre le flux de contrôle, il se passe si peu de choses ici qu'il n'est pas difficile de suivre la logique de contrôle du programme - chaque fois qu'une méthode est appelée sur le proxy Set, elle est envoyée à un gestionnaire d'appels. , qui appelle simplement la méthode souhaitée de manière réfléchie sur son objet enveloppé associé. Bien sûr, un proxy qui ne fait rien n’a aucun sens, n’est-ce pas ?

Il existe en fait une bonne utilité pour un wrapper vide comme SetProxyFactory - il peut être utilisé pour restreindre en toute sécurité une référence à un objet à une interface spécifique (ou un ensemble d'interfaces) afin que l'appelant ne puisse pas accéder accidentellement à la référence, ce qui la rend plus sûre. références à l'objet à du code non fiable tel que des plugins ou des rappels. Le listing 3 contient un ensemble de définitions de classes qui implémentent un scénario de rappel typique ; Vous verrez comment les proxys dynamiques peuvent remplacer plus facilement le modèle d'adaptateur, qui est généralement implémenté manuellement (ou par les assistants de génération de code fournis par l'EDI).

Listing 3. Scénario de rappel typique
interface publique ServiceCallback ( public void doCallback(); ) interface publique Service ( public void serviceMethod(ServiceCallback callback); ) public class ServiceConsumer implémente ServiceCallback (private Service service; ... public void someMethod() ( ... service.serviceMethod( ce);

La classe ServiceConsumer implémente ServiceCallback (qui est généralement un moyen pratique de prendre en charge les rappels) et transmet cette référence à serviceMethod() comme référence de rappel. Le problème avec cette approche est qu'il n'est pas possible d'empêcher l'implémentation de Service de diffuser un ServiceCallback vers ServiceConsumer et d'appeler des méthodes que ServiceConsumer ne souhaite pas appeler sur Service . Parfois, vous ne pensez pas à ce danger, et parfois vous le faites. Si vous vous posez la question, vous pouvez faire de l'appelable une classe interne ou écrire une classe d'adaptateur vide (voir ServiceCallbackAdapter dans le listing 4) et envelopper le ServiceConsumer avec un ServiceCallbackAdapter . ServiceCallbackAdapter empêche Service de diffuser ServiceCallback vers ServiceConsumer .

Listing 4. Une classe d'adaptateur qui limite en toute sécurité un objet à une interface afin qu'il ne puisse pas être exécuté par du code hostile
classe publique ServiceCallbackAdapter implémente ServiceCallback (private final ServiceCallback cb; public ServiceCallbackAdapter(ServiceCallback cb) ( this.cb = cb; ) public void doCallback() ( cb.doCallback(); ) )

L'écriture de classes d'adaptateur telles que ServiceCallbackAdapter est simple mais fastidieuse. Vous devez écrire une méthode de répartition pour chaque méthode dans l'interface packagée. Dans le cas de ServiceCallback, une seule méthode devait être implémentée, mais certaines interfaces, telles que les interfaces Collections ou JDBC, contiennent plusieurs méthodes. Les IDE modernes réduisent la quantité de travail nécessaire à l'écriture d'une classe d'adaptateur en utilisant l'assistant "Méthodes déléguées", mais vous devez toujours écrire une classe d'adaptateur pour chaque interface que vous souhaitez empaqueter. Il y a un problème avec les classes contenant uniquement du code généré. Il semble qu'il existe un moyen d'exprimer de manière plus concise le "modèle d'adaptateur de rétrécissement sans rien faire".

Classe d'adaptateur générique

La classe SetProxyFactory est certes plus compacte que la classe adaptateur équivalente pour l'interface Set, mais elle ne fonctionne que sur une seule interface : Set. Mais en utilisant des adaptateurs génériques, vous pouvez facilement créer une fabrique de proxy générique qui peut faire la même chose pour n'importe quelle interface, comme le montre le listing 5. Elle est presque identique à SetProxyFactory, mais peut fonctionner pour n'importe quelle interface. Désormais, vous n'aurez plus jamais à réécrire votre classe d'adaptateur plus étroite ! Si vous souhaitez créer un objet proxy qui affinera en toute sécurité un objet à l'interface T , appelez simplement getProxy(T.class,object) et vous l'obtiendrez sans la pile supplémentaire de classes d'adaptateur.

Liste 5. Classe d'usine d'adaptateur conique universel
classe publique GenericProxyFactory ( public static T getProxy(Classe intf, final T obj) ( return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), new Class ( intf), new InvocationHandler() ( public Object invoqué (Object proxy, méthode méthode, Object args) lance Throwable ( return method.invoke(obj, args); ) ));

Des proxys dynamiques en tant que décorateurs

Bien entendu, les outils de proxy dynamique peuvent faire plus que simplement affiner le type d’objet à une interface spécifique. Il n'y a qu'un court passage d'un simple adaptateur restreint à un modèle de décorateur, où le proxy ajoute des fonctionnalités supplémentaires aux appels, telles que des contrôles de sécurité ou l'enregistrement de données. Le listing 6 montre le consignateur InvocationHandler, qui écrit un message de journal montrant la méthode invoquée, les paramètres de qualification et la valeur de retour, en plus d'appeler simplement la méthode sur la cible souhaitée. À l'exception de l'appel réflexif à Ensure() , tout le code ici n'est que la partie génération du message de débogage et une très, très petite partie. Le code de la méthode de fabrique de proxy est presque identique à celui de GenericProxyFactory , sauf qu'il utilise un LoggingInvocationHandler au lieu d'un gestionnaire d'appels anonyme.

Listing 6. Un décorateur basé sur un proxy qui génère un syslog de débogage pour chaque appel de méthode
classe statique privée LoggingInvocationHandler implémente InvocationHandler ( final T sous-jacent ; public LoggingHandler(T sous-jacent) ( this.underlying = sous-jacent ; ) public Object invoque (Object proxy, Method méthode, Object args) throws Throwable ( StringBuffer sb = new StringBuffer(); sb.append(method .getName()); sb.append("("); for (int i=0; args != null && i "); sb.append(ret); ) System.out.println(sb); return ret; ) )

Si vous encapsulez un HashSet à l'aide d'un proxy de journalisation et exécutez le programme de test simple suivant :

Set s = newLoggingProxy(Set.class, new HashSet());

s.add("trois");

if (!s.contains("quatre")) s.add("quatre");

System.out.println(s);

À ce stade, je m'attends à ce que les fans d'AOP explosent pratiquement avec une phrase du genre : "Mais c'est à cela que sert AOP !" C’est vrai, mais il existe plusieurs façons de résoudre un problème donné – et ce n’est pas parce qu’une méthode peut résoudre un problème donné que cette solution est la meilleure. Dans tous les cas, l'approche proxy dynamique a l'avantage de fonctionner entièrement en « Java pur » et tous les centres de données n'utilisent pas AOP (ou ne devraient pas l'utiliser).

Proxy dynamiques comme adaptateurs

Les proxys peuvent également être utilisés comme de véritables adaptateurs, fournissant une représentation d'un objet qui exporte une interface différente de celle implémentée par l'objet sous-jacent. Le gestionnaire d'appels n'a pas besoin d'envoyer chaque appel de méthode au même objet sous-jacent ; il peut examiner le nom et envoyer différentes méthodes à différents objets. À titre d'exemple, supposons que vous disposiez d'un ensemble d'interfaces JavaBeans pour représenter des objets persistants (Person , Company et PurchaseOrder ) qui définissent des getters et des setters pour les propriétés, et que vous créiez une couche de persistance qui convertit les enregistrements de base de données en objets qui implémentent ces interfaces. Au lieu d'écrire ou de générer une classe pour chaque interface, vous pouvez avoir une classe proxy générique de style JavaBeans qui stocke les propriétés dans une carte.

Le listing 7 affiche un proxy dynamique qui détermine le nom de la méthode appelée et implémente les méthodes getter et setter directement en consultant ou en modifiant la carte des propriétés. Cette classe proxy peut désormais implémenter des objets de plusieurs interfaces de style JavaBeans.

Listing 7. Classe proxy dynamique qui envoie des getters et des setters à la carte
classe publique JavaBeanProxyFactory ( classe statique privée JavaBeanProxy implémente InvocationHandler ( Map propriétés = nouveau HashMap (); propriétés) ( this.properties.putAll(properties); ) public Object Ensure (Object proxy, Method method, Object args) throws Throwable ( String meth = method.getName(); if (meth.startsWith("get")) ( String prop = meth.substring(3); Object o = Properties.get(prop); if (o != null && !method.getReturnType().isInstance(o)) throw new ClassCastException(o.getClass().getName () + " n'est pas un " + method.getReturnType().getName()); else if (meth.startsWith("set")) ( // Répartir les setters de la même manière) else if (meth.startsWith( "is") ) ( // Version alternative de get pour les propriétés booléennes ) else ( // Peut distribuer des méthodes non get/set/is comme vous le souhaitez ) ) ) public static T getProxy(Classe intf, Carte valeurs) return (T) Proxy.newProxyInstance(JavaBeanProxyFactory.class.getClassLoader(), new Class ( intf ), new JavaBeanProxy(values));

) )

Bien qu'il existe une perte potentielle de sécurité de type due au fait que la réflexion fonctionne en termes d'Object , un getter opérant dans un JavaBeanProxyFactory "échoue de force" certaines des vérifications supplémentaires de correspondance de type requises, comme la vérification isInstance() pour le getter dans mon exemple.

Frais d'exécution

Sans entrer dans le sujet de l'écriture de programmes de test, j'ai écrit un programme de test simple et "non scientifique" qui crée une boucle en entrant des données dans un ensemble, en insérant, recherchant et supprimant de manière aléatoire des éléments de l'ensemble. Je l'ai implémenté sous la forme de trois implémentations de Set : un HashSet sans fioritures, un adaptateur Set écrit à la main qui transmet simplement toutes les méthodes au HashSet sous-jacent et un adaptateur Set basé sur un proxy qui transmet également simplement toutes les méthodes au HashSet sous-jacent. Chaque itération de la boucle générait plusieurs nombres aléatoires et effectuait une ou plusieurs opérations Set. L'adaptateur saisi manuellement n'a généré que quelques pour cent de la surcharge par rapport à un simple HashSet (probablement en raison d'une mise en cache intégrée efficace au niveau de la JVM et d'une prédiction de branche au niveau matériel) ; L'adaptateur proxy était nettement plus lent qu'un simple HashSet, mais la surcharge était inférieure de moitié.

Ma conclusion de l'expérience est que dans la grande majorité des cas, la technologie de proxy dynamique fonctionne assez bien pour les méthodes légères et que, à mesure que les opérations dans lesquelles des proxys sont utilisés deviennent plus lourdes (par exemple, les appels de méthodes distants ou les méthodes utilisant la sérialisation, effectuez I/ O, ou récupérer des données de la base de données), la surcharge du proxy approchera de zéro. Même s'il y aura certainement des cas dans lesquels la technologie de proxy dynamique entraînera une surcharge d'exécution inacceptable, ceux-ci constitueront probablement une minorité des situations.

Conclusion

Les proxys dynamiques sont un outil puissant et sous-utilisé dans la mise en œuvre de nombreux modèles de conception, notamment Proxy, Decorator et Adapter. Les implémentations basées sur un proxy de ces modèles sont simples à écrire, plus difficiles à se tromper et plus polyvalentes ; dans de nombreux cas, une seule classe proxy dynamique peut servir de décorateur ou de proxy pour toutes les interfaces, plutôt que d'écrire une classe statique pour chaque interface. Pour toutes les applications, à l'exception des applications les plus critiques en termes de performances, la technologie de proxy dynamique peut être préférable à la technologie de codage manuel ou de stub généré.

  • Résumé du constructeur

    Constructeurs
    Modificateur Constructeur et description
    protégé Procuration(Gestionnaire d'appels h)
  • Résumé de la méthode

    Méthodes
    Modificateur et type Méthode et description
    Gestionnaire d'appels statique getInvocationHandler(Proxy d'objet)
    Classe statique getProxyClass(chargeur ClassLoader, classe...interfaces)

    Renvoie l'objet java.lang.Class pour une classe proxy étant donné un chargeur de classe et un tableau d'interfaces.

    booléen statique estProxyClass(Classecl)

    Renvoie true si et seulement si la classe spécifiée a été générée dynamiquement pour être une classe proxy à l'aide de la méthode getProxyClass ou de la méthode newProxyInstance.

    Objet statique nouvelleInstanceProxy(chargeur ClassLoader, classeinterfaces, InvocationHandlerh)

    Renvoie une instance d'une classe proxy pour les interfaces spécifiées qui distribue les appels de méthode au gestionnaire d'appel spécifié.

    • Méthodes héritées de la classe java.lang.Object

      clone , égal à , finaliser , getClass , hashCode , notify , notifyAll , toString , attendre , attendre , attendre
    • Détails du champ

      • h

        gestionnaire d'appels protégé h

        le gestionnaire d'appel pour cette instance de proxy.

    • Détail du constructeur

      • Procuration

        Proxy protégé (InvocationHandler h)

        Construit une nouvelle instance Proxy à partir d'une sous-classe (généralement, une classe proxy dynamique) avec la valeur spécifiée pour son gestionnaire d'appel.

        Paramètres : h - le gestionnaire d'appel pour cette instance de proxy
    • Détail de la méthode

      • getProxyClass

        Classe statique publiquegetProxyClass (chargeur ClassLoader, classe... interfaces) lance IllegalArgumentException

        Renvoie l'objet java.lang.Class pour une classe proxy étant donné un chargeur de classe et un tableau d'interfaces. La classe proxy sera définie par le chargeur de classe spécifié et implémentera toutes les interfaces fournies. Si une classe proxy pour la même permutation d'interfaces a déjà été définie par le chargeur de classe, alors la classe proxy existante sera renvoyée ; sinon, une classe proxy pour ces interfaces sera générée dynamiquement et définie par le chargeur de classe.

        Il existe plusieurs restrictions sur les paramètres pouvant être transmis à Proxy.getProxyClass :

        • Tous les objets Class du tableau interfaces doivent représenter des interfaces, et non des classes ou des types primitifs.
        • Deux éléments du tableau interfaces ne peuvent pas faire référence à des objets Class identiques.
        • Tous les types d'interface doivent être visibles par leur nom via le chargeur de classe spécifié. En d'autres termes, pour le chargeur de classe cl et chaque interface i , l'expression suivante doit être vraie : Class.forName(i.getName(), false, cl) == i
        • Toutes les interfaces non publiques doivent être dans le même package ;
        • sinon, il ne serait pas possible pour la classe proxy d'implémenter toutes les interfaces, quel que soit le package dans lequel elle est définie.
          • Si le type de retour de l’une des méthodes est un type primitif ou vide, alors toutes les méthodes doivent avoir le même type de retour.
          • Sinon, l’une des méthodes doit avoir un type de retour attribuable à tous les types de retour des autres méthodes.
        • La classe proxy résultante ne doit pas dépasser les limites imposées aux classes par la machine virtuelle. Par exemple, la VM peut limiter le nombre d’interfaces qu’une classe peut implémenter à 65 535 ; dans ce cas, la taille du tableau d'interfaces ne doit pas dépasser 65535.

        Si l'une de ces restrictions n'est pas respectée, Proxy.getProxyClass lancera une IllegalArgumentException . Si l'argument du tableau de l'interface ou l'un de ses éléments est null , une NullPointerException sera levée.IllegalArgumentException

        Renvoie une instance d'une classe proxy pour les interfaces spécifiées qui distribue les appels de méthode au gestionnaire d'appel spécifié. Cette méthode est équivalente à : Proxy.getProxyClass(loader, interfaces).

        getConstructor (nouvelle classe ( InvocationHandler.class )).

        newInstance(nouvel objet (gestionnaire));
      • Proxy.newProxyInstance lève IllegalArgumentException pour les mêmes raisons que Proxy.getProxyClass.

        Paramètres : loader - le chargeur de classe pour définir les interfaces de la classe proxy - la liste des interfaces pour que la classe proxy implémente h - le gestionnaire d'invocation pour distribuer les invocations de méthode à Retourne : une instance proxy avec le gestionnaire d'invocation spécifié d'une classe proxy qui est défini par le chargeur de classe spécifié et qui implémente les interfaces spécifiées. Lance : IllegalArgumentException - si l'une des restrictions sur les paramètres pouvant être transmises à getProxyClass est violée. NullPointerException - si l'argument du tableau des interfaces ou l'un de ses éléments est null , ou si le le gestionnaire d'appel, h , est nulcl)

        estProxyClass

        public statique booléen isProxyClass(Classe

        Renvoie true si et seulement si la classe spécifiée a été générée dynamiquement pour être une classe proxy à l'aide de la méthode getProxyClass ou de la méthode newProxyInstance.
      • La fiabilité de cette méthode est importante pour pouvoir l'utiliser pour prendre des décisions de sécurité, son implémentation ne doit donc pas simplement tester si la classe en question étend Proxy .

        Paramètres : cl - la classe à tester Renvoie : true si la classe est une classe proxy et false sinon. Lance : NullPointerException - si cl est null

        Renvoie le gestionnaire d'appel pour l'instance de proxy spécifiée.

        Paramètres : proxy - l'instance proxy pour renvoyer le gestionnaire d'appel pour Returns : le gestionnaire d'appel pour l'instance proxy Throws : IllegalArgumentException - si l'argument n'est pas une instance proxy

Classes proxy dynamiques

Aujourd'hui, nous allons parler d'une fonctionnalité aussi intéressante JVM, comme les classes proxy dynamiques. Supposons que nous ayons une classe UN, qui implémente certaines interfaces. La machine Java peut générer une classe proxy pour une classe donnée au moment de l'exécution UN, c'est-à-dire une classe qui implémente toutes les interfaces de la classe UN, mais remplace l'appel à toutes les méthodes de ces interfaces par un appel à la méthode InvocationHandler#invoke, Où Gestionnaire d'appels-Interface JVM, pour lequel vous pouvez définir vos propres implémentations.


Une classe proxy est créée à l'aide d'un appel de méthode Proxy.getProxyClass, qui prend une classe de chargeur et un tableau d'interfaces ( interfaces), et renvoie un objet de classe java.lang.Class, qui est chargé à l'aide de la classe de chargeur transmise et implémente le tableau d'interfaces transmis.

Il existe un certain nombre de restrictions sur les paramètres transmis :

1. Tous les objets d'un tableau interfaces doit être des interfaces. Ils ne peuvent pas être des classes ou des primitifs.

2. Dans un tableau interfaces il ne peut pas y avoir deux objets identiques.

3. Toutes les interfaces dans un tableau interfaces doit être chargé par la classe de chargement transmise à la méthode getProxyClass.

4. Toutes les interfaces non publiques doivent être définies dans le même package, sinon la classe proxy générée ne pourra pas toutes les implémenter.

5. Deux interfaces ne peuvent pas avoir une méthode avec le même nom et la même signature de paramètre, mais avec des types de valeurs de retour différents.

6. Longueur du tableau interfaces limité à 65 535 interfaces. Non Java-une classe ne peut pas implémenter plus de 65535 interfaces (et c'est ce que je voulais !).

Si l'une des restrictions ci-dessus n'est pas respectée, une exception sera levée IllegalArgumentException, et si le tableau d'interfaces interfaces est égal nul, il sera jeté NullPointerException.

Propriétés de classe proxy dynamique

Il faut dire quelques mots sur les propriétés de la classe créée à l'aide de Proxy.getProxyClass. Ces propriétés sont les suivantes :

1. La classe proxy est publique, équipée d'un modificateur final et n'est pas abstrait.

2. Le nom de la classe proxy n'est pas défini par défaut, mais commence par $Procuration. Tous les espaces de noms commençant par $Procuration réservé aux classes proxy.

3. La classe proxy hérite de java.lang.reflect.Proxy.

4. La classe proxy implémente toutes les interfaces transmises lors de la création, dans l'ordre dans lequel elles ont été transmises.

5. Si une classe proxy implémente une interface non publique, alors elle sera générée dans le package dans lequel cette interface non publique est définie. En général, le package dans lequel la classe proxy sera générée n'est pas défini.

6. Méthode Proxy.isProxyClass retours vrai pour les classes créées avec Proxy.getProxyClass et pour les classes d'objets créées avec Proxy.newProxyInstance Et FAUX sinon. Cette méthode est utilisée par le sous-système de sécurité Java et vous devez comprendre que pour une classe simplement héritée de java.lang.reflect.Proxy il reviendra FAUX.

7. java.security.ProtectionDomain pour la classe proxy est la même que pour les classes système chargées bootstrap- un bootloader, par exemple - pour java.lang.Object. C'est logique, car le code de la classe proxy est créé par JVM et elle n'a aucune raison de ne pas se faire confiance.

Une instance d'une classe proxy dynamique et ses propriétés

Le constructeur de classe proxy prend un argument : l'implémentation de l'interface Gestionnaire d'appels. Par conséquent, un objet de classe proxy peut être créé à l'aide de la réflexion en appelant la méthode nouvelleInstance objet de classe Classe. Cependant, il existe un autre moyen : appeler la méthode Proxy.newProxyInstance, qui prend en entrée un chargeur de classe, un tableau d'interfaces que la classe proxy implémentera et un objet qui implémentera Gestionnaire d'appels. En fait, cette méthode combine l'obtention d'une classe proxy avec Proxy.getProxyClass et créer une instance de cette classe par réflexion.

Propriétés de l'instance de classe proxy créée ce qui suit:

1. Nous réduisons l'objet de classe proxy à toutes les interfaces passées dans le tableau interfaces. Si IDémo- une des interfaces transférées, puis l'opération instance proxy d'IDemo reviendra toujours vrai, et l'opération (IDémo) proxy se terminera correctement.

2. Méthode statique Proxy.getInvocationHandler renvoie le gestionnaire d'appels transmis lors de l'instanciation de la classe proxy. Si l'objet passé à cette méthode n'est pas une instance de la classe proxy, il lancera IllegalArgumentException exception.

3. La classe du gestionnaire d'appels implémente l'interface Gestionnaire d'appels, qui définit la méthode invoquer, portant la signature suivante :

Ici procuration- une instance d'une classe proxy qui peut être utilisée lors du traitement d'un appel à une méthode particulière. Deuxième paramètre - méthode est une instance de la classe java.lang.reflect.Méthode. La valeur de ce paramètre est l'une des méthodes définies dans l'une des interfaces ou leurs super-interfaces transmises lors de la création de la classe proxy. Le troisième paramètre est un tableau de valeurs d’arguments de méthode. Les arguments des types primitifs seront remplacés par des instances de leurs classes wrapper, telles que java.lang.Boolean ou java.lang.Integer. Mise en œuvre spécifique de la méthode invoquer peut modifier ce tableau.

Valeur de retour de la méthode invoquer doit être d'un type compatible avec le type de retour de la méthode d'interface sur laquelle ce wrapper est appelé. En particulier, si une méthode d'interface renvoie une valeur d'un type primitif, elle doit renvoyer une instance de la classe wrapper de ce type primitif. Si retourné nul, mais une valeur d'un type primitif est attendue - sera jetée NullPointerException. Dans le cas de types non primitifs, la classe de valeur de retour de la méthode invoquer doit être castable dans la classe de la valeur de retour de la méthode d'interface, sinon elle sera renvoyée ClassCastException.

À l'intérieur d'une méthode invoquer Seules les exceptions vérifiées qui sont définies dans la signature de la méthode d'interface appelée ou qui peuvent y être converties doivent être levées. En dehors de ces types d'exceptions, seules les exceptions non contrôlées (telles que java.lang.RuntimeException) ou des erreurs (par exemple, java.lang.Erreur). Si à l'intérieur d'une méthode invoquer Si une exception vérifiée est levée et qu'elle est incompatible avec celles décrites dans la signature de la méthode d'interface, alors une UndeclaredThrowableException sera également levée.

Méthodes code de hachage, est égal Et versChaîne défini dans la classe Objet, sera également appelé non pas directement, mais via la méthode invoquer avec toutes les méthodes d’interface. Autres méthodes publiques de la classe Objet sera appelé directement.

Exemple : Utilisation de classes proxy pour un DAO générique

Regardons l'exemple promis d'utilisation de classes proxy dynamiques. Idée tirée de l'article Ne répétez pas DAO ! , seulement nous essaierons de l'implémenter sans utiliser Printemps. Le point est le suivant : nous avons Hiberner, dans lequel il existe des requêtes nommées. Nous avons beaucoup DAO pour différents types d'entités qui disposent de méthodes de recherche d'objets basées sur certains critères, de comptage du nombre d'objets, etc. De plus, chaque méthode, en fait, appelle simplement l'une ou l'autre requête nommée et renvoie son résultat. On ne sait pas pourquoi il existe plusieurs méthodes avec la même logique. Vous pouvez simplement définir des méthodes dans les interfaces appropriées et les appeler via un proxy vers ces interfaces. Dans un proxy, l'appel de méthode est remplacé par un appel à la requête nommée correspondante (dont le nom peut être calculé par exemple à l'aide de la formule ENTITY_NAME-METHOD_NAME). Il est vrai qu'il y a une difficulté - dans le sens même GénériqueDao Il existe des méthodes qui n'ont pas besoin d'être remplacées, en particulier c'est la méthode charger, qui charge un objet de la base de données et une méthode sauvegarder- sauvegarder l'objet dans la base de données, respectivement.

Tout d'abord, l'interface IGénériqueDao:

importer ;

interface publique IGenericDao< T extends Entity> {

public Tload (identifiant sérialisable) ;

public void save (entité T) ;

public String getName() ;

Au lieu de sa mise en œuvre complète, je ne montrerai qu'un petit bout. L'implémentation elle-même est facile à écrire :

nom du package.samolisov.proxy.dao ;

importer java.io.Serialised ;

importer nom.samolisov.proxy.dao.entity.Entity;

classe publique GenericDao< T extends Entity>implémente IGenericDao< T> {

cours privé< T>clazz;

public GenericDao (Classe< T>clazz) (

ce .clazz = clazz;

charge T publique (identifiant sérialisable) (

Système .out .println ( "invoquer GenericDao#load, id = "+ identifiant);

renvoie null ;

sauvegarde publique vide (entité T) (

Système .out .println ( "facture GenericDao#save, entité = "+entité);

chaîne publique getName() (

return clazz.getSimpleName();

Maintenant, le plus important est GénériqueDaoProxy. Nous allons déplacer la création d'un proxy dans une méthode statique nouvelleInstance qui acceptera une instance de la classe GénériqueDao, paramétré par le type d'entité dont nous avons besoin, et une liste d'interfaces - DAO pour cette entité. Encore une fois, je donnerai le code du stub, dans lequel, au lieu de System.out.println("sera demandé par nom " + dao.getName() + "-" + method.getName()); renvoie null ; vous devrez insérer une véritable adresse pour Hiberner:

nom du package.samolisov.proxy.dao ;

importer java.lang.reflect.InvocationHandler;

importer java.lang.reflect.InvocationTargetException;

importer java.lang.reflect.Method ;

importer java.lang.reflect.Proxy ;

importer nom.samolisov.proxy.dao.entity.Entity;

classe publique GenericDaoProxy< T extends Entity>implémente InvocationHandler (

privé IGenericDao< T>dao ;

privé GenericDaoProxy(IGenericDao< T>dao) (

ceci .dao = dao ;

@SuppressWarnings("non coché")

public statique< T extends Entity>IGénériqueDao< T>nouvelleInstance(IGenericDao< T>dao, Classe...interf) (

retour (IGénériqueDao< T> ) Procuration.newProxyInstance(

Dao.getClass().getClassLoader(),

nouveau GenericDaoProxy< T>(dao) ) ;

publique Objet invoquer( Objet proxy, Méthode méthode, Objet args) lance Throwable (

ClassedéclarantClass = méthode.getDeclaringClass();

pour (Classeinterface : dao.getClass().getInterfaces()) (

if (declaringClass.isAssignableFrom (interf) ) (

essayer (

return method.invoke(dao, args) ;

) catch (InvocationTargetException e) (

lancer e.getTargetException();

System .out .println ("sera demandé par nom " + dao.getName() + "-" + method.getName() ) ;

renvoie null ;

Regardons de plus près la méthode invoquer. Dans cette méthode, nous calculons d'abord où la méthode représentée par le paramètre a été définie méthode. Si cette méthode a été définie dans la classe d'objet dao ou dans l'une des interfaces implémentées par cette classe, alors nous l'appelons simplement. Ainsi, nous implémentons des appels de méthode directs charger, sauvegarder et d'autres définis dans la classe GénériqueDao. Si la méthode appelée est définie en dehors de la classe GénériqueDao ou ses interfaces, alors on se tourne vers Hiberner avec une requête nommée.

Voyons maintenant comment ces structures peuvent être utilisées :

IUserDao dao = (IUserDao)GenericDaoProxy.newInstance (nouveau GenericDao< User>(Utilisateur.class), IUserDao.class);

Dao.findAllUsers();

Dao.load(10);

Je répète que l'objet proxy est réductible à n'importe quelle interface pour laquelle il a été créé. Dans notre cas, c'est l'interface IUserDao, qui définit la méthode trouverTous les utilisateurs(). Lorsque cette méthode est appelée, une requête sera faite à Hiberner. Méthode charger sera appelé directement, car il est défini dans la classe GénériqueDao.

Je note que c'est grâce à l'utilisation de classes proxy dynamiques que cela fonctionne AOP du printemps, et parce que tous les haricots dedans Printemps sont créés par une usine spéciale et stockés dans IoC-conteneur, le programmeur peut ne pas savoir exactement ce qui sera créé - un objet de la classe souhaitée ou un proxy pour cet objet. Cela peut poser des problèmes si votre code utilise des conversions de type explicites vers les classes. Le transtypage de type, lorsqu'un type est converti non pas en interface, mais directement en classe, constitue une violation des principes du concept d'inversion de dépendance et, à l'amiable, ne doit pas être utilisé du tout.

Il convient également de noter qu'en plus des outils de proxy définis dans JVM, il existe des bibliothèques, par exemple avec de plus grandes capacités. Cependant, c'est un sujet pour une autre conversation.

Si vous avez aimé le message, abonnez-vous au blog ou lisez-moi sur



Avez-vous aimé l'article? Partagez avec vos amis !