Préface
"Dynamic Agent" provient en fait du mode proxy en mode conception, et le mode proxy utilise l'objet proxy pour compléter les demandes de l'utilisateur et bloque l'accès des utilisateurs aux objets réels.
Pour donner l'exemple le plus simple, nous voulons "fq" pour accéder aux sites Web étrangers, car nous n'avons pas tous les IPS étrangers, vous pouvez envoyer votre datagramme de demande aux hôtes étrangers qui ne sont pas bloqués, puis vous transmettez la demande à la destination en configurant l'hôte étranger et en le transmettant à notre hôte domestique après avoir reçu le message de réponse.
Dans cet exemple, un hôte étranger est un objet proxy, et les hôtes qui sont déposés par le mur sont de vrais objets. Nous ne pouvons pas accéder directement aux objets réels, mais nous pouvons y accéder indirectement via un proxy.
Un avantage du mode proxy est que toutes les demandes externes passent par l'objet proxy, et l'objet proxy a le droit de contrôler si vous êtes autorisé à vraiment accéder à l'objet réel. S'il y a une demande illégale, l'objet proxy peut vous rejeter complètement sans problème avec l'objet réel.
L'une des applications les plus typiques du mode proxy est le framework Spring. L'AOP de Spring utilise la programmation orientée vers l'aspect pour isoler la logique métier réelle à partir des exceptions de journaux connexes et d'autres informations. Chaque fois que vous demandez la logique métier, elle correspond à un objet proxy. En plus d'effectuer les vérifications d'autorisation et l'impression du journal nécessaires, cet objet proxy est le véritable bloc de traitement de la logique métier.
Proxy statique
Il existe deux principaux implémenteurs du modèle proxy, "proxy statique" et "proxy dynamique". La différence essentielle entre ces deux est que la première classe de proxy nécessite un codage manuel par les programmeurs, tandis que la dernière classe de proxy est automatiquement générée. C'est pourquoi vous avez à peine entendu parler du concept de "proxy statique". Bien sûr, il est naturellement plus facile de comprendre le "proxy dynamique".
Une chose que vous devez être claire est que l'objet proxy indique toutes les méthodes de l'objet réel, c'est-à-dire que l'objet proxy doit fournir au moins le même nom de méthode que l'objet réel pour l'appel, donc un objet proxy doit définir toutes les méthodes appartenant à l'objet réel, y compris les méthodes de la classe parent.
Regardons un exemple de proxy statique simple:
Pour illustrer le problème, nous définissons une interface Iservice et laissons notre vraie classe hériter et implémenter l'interface, afin qu'il existe deux méthodes dans notre classe réelle.
Alors, comment définir une classe de proxy pour compléter le proxy des vrais objets?
D'une manière générale, l'essence d'une classe proxy est de définir toutes les méthodes de la classe réelle et d'ajouter d'autres opérations à l'intérieur de la méthode, et enfin d'appeler la méthode de la classe réelle.
La classe proxy doit proxyer toutes les méthodes de la classe réelle, c'est-à-dire que des méthodes qui sont exactement les mêmes que ces méthodes de la classe réelle, et la méthode de la classe réelle sera indirectement appelée à l'intérieur de ces méthodes.
Donc, d'une manière générale, la classe proxy choisira d'hériter directement toutes les interfaces et classes parents de la classe réelle afin d'obtenir toutes les signatures de méthodes de parent de la classe réelle, c'est-à-dire pour d'abord toutes les méthodes de parent.
Ensuite, le proxy n'est pas une méthode parent dans la classe réelle. Dans l'exemple ici, la méthode Doservice est la propre méthode de la classe réelle. Notre classe de proxy doit également définir une méthode avec la même signature de méthode pour la procurer.
De cette façon, même si notre classe proxy est terminée, toutes les méthodes de la classe réelle peuvent être proxyes via la classe de procuration à l'avenir. Comme ça:
public static void main (String [] args) {realClass realClass = new realClass (); ProxyClass proxyclass = new proxyclass (realClass); proxyclass.sayhello (); proxyclass.doservice ();}Proxyclass, en tant qu'objet de classe proxy, peut proxyer toutes les méthodes de la classe réelle et imprimer certaines informations "insignifiantes" avant l'exécution de ces méthodes.
Il s'agit essentiellement de l'idée de mise en œuvre de base du modèle de proxy, mais la différence entre le proxy dynamique et ce type de proxy statique est que le proxy dynamique ne nécessite pas les définitions de nos méthodes une par une, et la machine virtuelle générera automatiquement ces méthodes pour vous.
Mécanisme de proxy dynamique JDK
Ce qui distingue le proxy dynamique du proxy statique, c'est que la classe proxy de proxy dynamique est créée dynamiquement par la machine virtuelle à l'exécution et effacée lorsque la machine virtuelle est désinstallée.
Nous réutilisons les classes utilisées dans le proxy statique ci-dessus pour voir comment le proxy dynamique de JDK peut proximité toutes les méthodes d'une instance d'une certaine classe.
Définissez une classe de traitement des gestionnaires:
L'API proxy dynamique dans la fonction principale appelle JDK pour générer une instance de classe proxy:
Il y a encore beaucoup de code impliqués, analysons-le petit à petit. Tout d'abord, RealClass implémente l'interface est de notre classe de proxy et définit son propre dosage de méthode en interne.
Ensuite, nous définissons une classe de traitement qui hérite de l'interface InvocationHandler et met en œuvre sa méthode invoquée uniquement déclarée. De plus, nous devons déclarer un champ membre pour stocker des objets réels, c'est-à-dire des objets proxy, car toute méthode que nous proxyons est essentiellement basée sur des méthodes pertinentes d'objets réels.
En ce qui concerne le rôle de cette méthode d'appel et la signification de divers paramètres formels, nous effectuerons une analyse détaillée lorsque nous reflétons le code source proxy.
Enfin, définissez notre classe de traitement et effectuez essentiellement un proxy dynamique basé sur JDK. La méthode principale est la méthode NewProxyInstance de la classe proxy. Cette méthode a trois paramètres. L'un est un chargeur de classe, le second est une collection de toutes les interfaces implémentées par la classe proxy, et la troisième est notre classe de processeur personnalisée.
La machine virtuelle utilise le chargeur de classe que vous fournissez au moment de l'exécution, charge toutes les classes d'interface spécifiées dans la zone de méthode, puis reflète et lit les méthodes de ces interfaces et combine la classe de processeur pour générer un type de proxy.
La dernière phrase peut être un peu abstraite. Comment «combiner avec des classes de processeurs pour générer un type de proxy»? À cet égard, nous spécifions les paramètres de démarrage de la machine virtuelle et le laissons enregistrer le fichier de classe généré de la classe proxy.
-DSUN.MISC.ProxyGenerator.SAveneratedFiles = True
Nous décompilons ce fichier de classe via des outils tiers et il y a beaucoup de contenu. Nous avons divisé l'analyse:
Tout d'abord, le nom de cette classe de procuration est très aléatoire. S'il y a plusieurs classes proxy à générer dans un programme, "$ proxy + numéro" est leur nom de classe.
Ensuite, vous remarquerez que cette classe proxy hérite de la classe de proxy et que l'interface est spécifiée (auparavant, si plusieurs interfaces étaient spécifiées, plusieurs interfaces seraient héritées ici).
Ensuite, vous constaterez que ce constructeur nécessite un paramètre de type InvocationHandler, et le corps du constructeur est de passer cette instance InvocationHandler au champ correspondant du proxy de classe parent pour le stockage. C'est également l'une des raisons pour lesquelles toutes les classes de proxy doivent utiliser le proxy comme classe parent, qui consiste à publier le champ InvocationHandler dans la classe parent. Nous saurons plus tard que cette petite conception entraînera un inconvénient mortel du proxy dynamique basé sur JDK, qui sera introduit plus tard.
Ce contenu est également une partie relativement importante de la classe de procuration. Il sera exécuté lorsque la machine virtuelle initialise statiquement la classe proxy. Ce grand morceau de code complète la fonction de refléter toutes les méthodes de l'interface, et toutes les méthodes réfléchies sont stockées correspondant à un champ de type de méthode.
De plus, la machine virtuelle reflète également trois méthodes courantes dans l'objet, c'est-à-dire que la classe de proxy procurera également l'objet réel hérité de l'objet.
Dans la dernière partie, ce que nous voyons, c'est que la machine virtuelle reflète toutes les méthodes à proxyer en fonction du bloc de code d'initialisation statique et génère des méthodes de proxy pour elles.
Ces méthodes ressemblent à beaucoup de code, mais en fait, ce ne sont qu'une ligne de code, qui élimine la classe de processeur stockée pendant l'instanciation à partir du proxy de classe parent et appelle sa méthode invoquée.
Les paramètres de la méthode sont fondamentalement les mêmes. Le premier paramètre est l'instance de classe proxy actuelle (il prouve qu'il est inutile de passer ce paramètre dans le passé), le deuxième paramètre est l'instance de méthode de la méthode, et le troisième paramètre est l'ensemble de paramètres formel de la méthode. Sinon, c'est nul.
Jetons maintenant un coup d'œil à la classe de processeur personnalisée:
Toutes les méthodes de classe proxy appellent la méthode invoquée de la classe de processeur et passeront dans la méthode actuelle de la classe proxy. Cette méthode invoquée peut choisir de faire en sorte que la méthode soit appelée normalement, ou sauter l'appel de la méthode, et même faire des choses supplémentaires avant et après l'appel de la méthode.
C'est l'idée principale du proxy dynamique JDK. Résumons brièvement l'ensemble du processus d'appel.
Tout d'abord, la définition d'une classe de processeur est essentielle, et elle doit être associée à un objet réel, c'est-à-dire l'instance de classe proxy.
Ensuite, nous appelons n'importe quelle méthode de la classe proxy de l'extérieur, et à partir du code source décompilé, nous savons que la méthode de la classe proxy appellera à la place la méthode invoquée du processeur et passera la signature de la méthode et le jeu de paramètres formel de la méthode.
Enfin, la question de savoir si la méthode peut être appelée normalement dépend de la question de savoir si le corps de la méthode d'invoquer le processeur appelle réellement la méthode.
En fait, le proxy dynamique implémenté sur la base de JDK est défectueux, et ces défauts ne sont pas faciles à corriger, donc CGLIB est populaire.
Certains défauts et lacunes
Mécanisme proxy unique
Je ne sais pas si vous avez remarqué que les exemples ci-dessus ne sont pas disponibles. La classe proxy générée par la machine virtuelle hérite de la classe de proxy afin de stocker publiquement ses propres instances de classe de processeur. Qu'est-ce que cela signifie?
L'héritage racine unique de Java vous indique que la classe de proxy ne peut plus hériter d'une autre classe, donc les méthodes de la classe parentale de la classe de procuration ne pourront naturellement pas obtenir, c'est-à-dire que la classe de procuration ne peut procurer aucune méthode de la classe parent dans la classe réelle.
En dehors de cela est un autre petit détail. Je me demande si vous l'avez remarqué. Je l'ai écrit comme ça.
La méthode Sayhello ici est l'interface implémentée, tandis que la méthode Doservice est une méthode qui appartient à RealClass. Mais nous ne voyons pas cette méthode à partir de la classe proxy, ce qui signifie que cette méthode n'est pas proxyée.
Par conséquent, le mécanisme de proxy dynamique de JDK est unique, et il ne peut que proxie méthodes dans la collection d'interface de la classe de proxy.
Valeur de retour hostile
Veuillez noter que NewProxyInstance renvoie une instance de la classe proxy "$ proxy0", mais elle est renvoyée comme type d'objet, et vous ne pouvez pas forcer l'instance d'objet au type "$ proxy0".
Bien que nous sachions que cette instance d'objet est en fait le type "$ proxy0", le type "$ proxy0" n'existe pas pendant la période de compilation, et le compilateur ne vous permettra naturellement pas de le forcer à un type inexistant. Par conséquent, il ne le forcera généralement que pour être l'une des interfaces implémentées par cette classe de proxy.
realClass rc = new realClass (); myhanlder hanlder = new myhanlder (rc); iService obj = (iService) proxy.newproxyinstance (rc.getClass (). GetClassOader (), new class [] {iService.class}, hanlder); obj.sayhello ();Sortie d'exécution du programme:
Début du proxy ...... Hello World ... Proxy terminant ......
Ensuite, la question revient. Si notre classe proxy implémente plusieurs interfaces, à quel type d'interface devriez-vous le forcer? Maintenant, en supposant que la classe proxy implémente les interfaces A et B, alors si la dernière instance est forcée à A, vous ne pouvez naturellement pas appeler toutes les méthodes de l'interface B implémentées par la classe proxy, et vice versa.
Cela conduit directement à un résultat. Vous devez savoir quelle méthode se trouve dans quelle interface. Si vous le forcez à l'interface correspondante avant d'appeler une méthode, elle est assez hostile.
Ce qui précède est ce que nous pensons n'est pas élégant dans le mécanisme de proxy dynamique basé sur JDK. Bien sûr, ses avantages sont certainement plus importants que ces inconvénients. Dans le prochain article, nous présenterons une bibliothèque proxy dynamique CGLIB largement utilisée par divers cadres. Sa couche sous-jacente est basée sur le framework d'opération ByteCode ASM et ne s'appuie plus sur l'héritage pour l'implémenter, résolvant parfaitement les lacunes du seul proxy de JDK.
Tous les codes, images et fichiers de l'article sont stockés dans le cloud sur mon github:
(https://github.com/singleyam/overview_java)
Vous pouvez également choisir de télécharger localement.
Résumer
Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.