[JAVA] Expliquons DynamicProxy aussi facilement que possible

1.Tout d'abord

La bibliothèque standard Java a une classe java.lang.reflect.Proxy. .. Cette classe a été ajoutée à partir de J2SE 1.3, et la fonction fournie est généralement appelée DynamicProxy (ci-après dénommée «** Dynamic Proxy **» en japonais). Depuis la sortie de J2SE 1.3 en mai 2000, le proxy dynamique est sorti depuis longtemps et est déjà une technologie morte, mais il est toujours utilisé par de nombreux frameworks et bibliothèques Java. En revanche, il semble que les programmeurs Java en général ne soient pas très connus.

Dans cet article, pour ceux qui comprennent la syntaxe de base de Java, nous présenterons des modèles de proxy généraux pour comprendre le proxy dynamique, expliquerons la nécessité d'un proxy dynamique, puis l'expliquerons. Explique comment utiliser le proxy dynamique de Java. Il montre également comment implémenter un proxy dynamique pour des langages autres que Java (C #, Python, EcmaScript).

Le code Java publié dans cet article a été confirmé pour fonctionner dans l'environnement suivant. (L'environnement d'exploitation des langages autres que Java sera décrit à tout moment.) De plus, certaines clauses d'importation requises pour la compilation sont omises dans le code Java publié.

Nom version
OpenJDK 11.0.1

2. Proxy "statique" (suite)

Comprendre comment utiliser le proxy dynamique java.lang.reflect.Proxy ne devrait pas être difficile pour les programmeurs qui connaissent Java. Cependant, afin de comprendre la nécessité d'un proxy dynamique et de l'utiliser efficacement, comprenez le proxy « statique </ b>», qui est l'opposé de « dynamique </ b>». besoin de le faire. Cela a été expliqué dans "Je vais expliquer le modèle de proxy aussi facilement que possible".

proxy_image_small.png

2-1. Examen du proxy statique

Dans "J'expliquerai le modèle de proxy aussi facilement que possible", ** Un sur plusieurs classes qui incarnent une interface commune. Introduit jusqu'à ** points où le traitement commun peut être défini avec le proxy de. Dans cet article, nous allons commencer par cet anti-pattern.

staticproxy_manyclass_small.png

2-2. Limitations du proxy statique

L'application d'un proxy statique a un anti-modèle.

Voici un exemple plus spécifique du "proxy qui ajoute un traitement commun pour plusieurs classes concrètes à une interface" montré ci-dessus.

Une implémentation de la couche modèle d'un site de vente définit trois classes: "** client ", " commande " et " produit **". Pour que cette classe accède au SGBDR dans le traitement de toutes les méthodes, il est nécessaire d'obtenir une connexion SGBDR et de démarrer une transaction avant d'exécuter la méthode. Ensuite, après l'exécution, il est nécessaire de valider ou de restaurer la transaction et de fermer la connexion.

Les trois classes sont «** add » pour créer des données, « update » pour mettre à jour les données et « search» pour rechercher des données. Il semblait que ce serait suffisant s'il y avait une méthode «» et « obtenir des détails **» pour obtenir des données détaillées. Par conséquent, ces trois classes sont des classes concrètes de l'interface appelée «Model». Ensuite, définissez une classe appelée TransactionBarrier, qui est un proxy pour le traitement SGBDR, avec une classe concrète de Model.

Il s'agit d'un diagramme de classes montrant la relation entre ces classes.

ecmodel_proxy_class_small.png

Dans une certaine mesure, la conception de la classe ci-dessus a bien fonctionné, mais un jour, j'ai rencontré des problèmes.

Les informations «client» doivent être supprimées en masse pour protéger les informations client.

Par conséquent, une méthode appelée "** Bulk Delete *" est ajoutée à "Clients", mais cette méthode "Batch Delete" nécessite également un pré-post-traitement du SGBDR par TransactionBarrier. Par conséquent, ajoutez également une méthode appelée " Supprimer tout *" à l'interface Modèle.

Ensuite, vous devrez ajouter le "* Bulk Delete *" ajouté à l'interface Model aux" Commandes "et aux" Produits "qui ne nécessitent pas la méthode" * Bulk Delete * ". Cependant, en réalité, nous ne voulons pas que «Order» et «Product» appellent «Delete all», alors ajoutez la balise «@ deprecated» à la méthode «Delete all» pour «Order» et «Product». J'ai dû lancer sans condition ʻUnsupportedOperationException` lorsque cette méthode a été appelée.

ecmodel_proxy_class_unsupport_small.png

Dans l'exemple ci-dessus, il est difficile de rendre compte de la gravité de la situation car une seule méthode spéciale a été ajoutée dans les trois classes, mais dans le développement réel du système, des dizaines à des centaines de modèles sont définis en fonction du projet. .. Ensuite, chaque fois que vous définissez votre propre méthode dans chaque modèle, les cas ci-dessus se produisent fréquemment, et un grand nombre de méthodes qui lancent ʻUnsuppportedOperationException` seront ajoutées à chaque classe.

La raison de tomber dans cette situation est que * un groupe de classes qui ne devrait pas être implémenté comme une interface est dérivé de force d'une interface parce que nous voulons implémenter un traitement commun avec un proxy *.

À l'origine, «** client », « commande » et « produit **» doivent avoir défini des interfaces et des proxy distincts, comme indiqué ci-dessous.

ideal_ecmode_proxy_small.png

Cependant, dans la conception de classe ci-dessus, la classe proxy doit être implémentée pour chaque interface, ce qui est très gênant. En premier lieu, même s'il existe une relation un-à-un entre l'interface et la classe comme décrit ci-dessus, si vous définissez un proxy qui implémente le même traitement pour chacun, même si vous implémentez le traitement commun directement dans la classe d'implémentation, cela prendra du temps à implémenter. Même les avantages de l'utilisation d'un proxy sont perdus dans la mesure où cela ne change pas.

Ce serait bien s'il y avait un proxy qui pourrait être défini dans une implémentation pour plusieurs interfaces différentes, mais ce n'est pas possible avec la syntaxe Java normale. Cependant, avec le proxy dynamique **, vous pouvez implémenter un proxy commun sur plusieurs interfaces **.

3. Proxy "dynamique"

Maintenant, introduisons un exemple d'implémentation utilisant le proxy dynamique java.lang.reflect.Proxy (ci-après dénommé" ** Proxy **"). </ p>

3-1. Exemple d'implémentation de la classe java.lang.reflect.Proxy

Tout d'abord, définissez la classe médiée par Proxy et son interface. Ce code est pour MyInterface et MyClass publiés dans" Je vais expliquer le modèle de proxy aussi facilement que possible ". Identique à la mise en œuvre.

MyInterface.java


public interface MyInterface {
    public String myMethod(String value);
}

MyClass.java


public class MyClass implements MyInterface {
    @Override
    public String myMethod(String value) {
        System.out.println("Method:" + value);
        return "Result";
    }
}

Pour utiliser Proxy, vous devez définir une classe concrète pour l'interface java.lang.reflect.InvocationHandler. La classe concrète de ʻInvocationHandlerimplémente la méthode ʻinvoke (Object, Method, Object []). Cette méthode ʻinvoke` est une méthode qui implémente un traitement proxy spécifique.

ʻLe code pour MyInvocationHandler dans l'exemple d'implémentation de la classe concrète de InvocationHandler. Dans le traitement proxy de MyInvocationHandler, MyInvocationHandler: startest sorti comme pré-traitement etMyInvocationHandler: exit` est sorti comme sortie standard en post-traitement.

MyInvocationHandler.java


import java.lang.reflect.*;

/** java.lang.reflect.Classe d'implémentation InvocationHandler qui implémente le traitement proxy spécifié dans Proxy*/
public class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target)  {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("DynamicProxy:before");
        Object result = method.invoke(target, args);
        System.out.println("DynamicProxy:before");

        return result;
    }
}

Pour utiliser le MyInvocationHandler défini ci-dessus, utilisez la méthode NewProxyInstance de Proxy pour obtenir l'objet de Proxy. Spécifiez l'objet de la classe MyInvocationHandler définie ci-dessus dans le troisième argument de la méthode newProxyInstance.

    MyClass myClassObj  = new MyClass();
    MyInvocationHandler myInvocationHandler = new MyInvocationHandler(myClassObj);
    MyInterface proxiedObj = (MyInterface)Proxy.newProxyInstance(MyClass.class.getClassLoader(),
        new Class[] { MyInterface.class },
        myInvocationHandler);
    System.out.println(proxiedObj.myMethod("Argument"));

C'est le résultat de l'exécution du code ci-dessus.

Résultat d'exécution


DynamicProxy:before
Method:Argument
DynamicProxy:after
Result

3-2. Méthode NewProxyInstance de la classe Proxy

Tout d'abord, je vais expliquer la méthode newProxyInstance de Proxy.

Vous pouvez obtenir l'objet Proxy avec newProxyInstance. L'utilisateur convertit l'objet de type ʻObject acquis en l'un des types du tableau de classes de l'interface spécifiée par le deuxième argument et l'utilise. La valeur de retour de cette méthode est de type ʻObject, mais c'est une classe dérivée de la classe Proxy et est une classe concrète de toutes les interfaces du deuxième argument.

    MyInterface proxiedObj = (MyInterface)Proxy.newProxyInstance(MyClass.class.getClassLoader(),
        new Class[] { MyInterface.class },
        myInvocationHandler);
  • Spécifiez le chargeur de classe dans le ** premier argument ** de newProxyInstance. Puisque le chargeur de classe peut être obtenu à partir de toutes les classes avec la méthode getClassLoader (), il peut être obtenu avec le getClassLoader () de n'importe quelle classe, mais un environnement utilisant de nombreuses chaînes de chargeur de classe telles que les applications Web. Il est préférable de spécifier autant que possible la classe implémentée de manière unique dans l'application, en supposant qu'elle sera utilisée dans. Ici, le chargeur de classe de MyClass, qui est une classe médiée par un proxy dynamique, est spécifié.
  • Spécifiez un tableau de classes d'interface dans le ** deuxième argument ** de newProxyInstance. En Java, une classe peut implémenter plusieurs interfaces, spécifiez donc un tableau afin de pouvoir générer un proxy dynamique qui implémente plusieurs interfaces.
  • Dans le ** troisième argument ** de newProxyInstance, spécifiez un objet de la classe concrète de ʻInvocationHandler. L'objet spécifié par cet argument est le processus qui est réellement exécuté à l'intérieur du proxy dynamique. Ici, nous avons en fait implémenté MyInvocationHandler, qui est une classe concrète de ʻInvocationHandler, donc cet objet est spécifié.

3-3. Méthode d'appel InvocationHandler

Ensuite, je vais expliquer la méthode ʻinvoke de l'interface ʻInvocationHandler implémentée dans MyInvocationHandler.

La méthode ʻInvokeimplémente le traitement proxy. Chaque fois qu'une méthode qui incarne l'interface d'un objetProxy est appelée, cette méthode ʻinvoke est appelée. La méthode ʻinvokeest passée comme argument quelle méthode de l'objetProxy` a été appelée avec quel argument.

** Si vous souhaitez implémenter un proxy général **, appelez la méthode de l'objet cible dans la méthode ʻinvoke. La méthode à appeler est passée comme argument de la méthode ʻinvoke, mais l'objet à appeler est détenu par la classe concrète de ʻInvocationHandler` elle-même.

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
  • L'objet de classe Proxy qui a appelé cette ʻinvoke est passé au ** premier argument ** de ʻinvoke.
  • Le ** second argument ** de ʻinvoke reçoit un objet de la classe Methodqui représente la méthode appelée par l'objetProxy. Dans le processus de ʻinvoke, la valeur de cet argument est utilisée pour appeler la méthode de l'objet réel.
  • Dans le ** 3ème argument ** de ʻinvoke, l'argument spécifié dans la méthode dans laquelle l'objet Proxy` est appelé est passé en tant que tableau Object. Cela utilise également la valeur de cet argument pour appeler la méthode de l'objet réel.

3-4 Qu'est-ce qu'un proxy dynamique et qu'est-ce que "dynamique"?

Si vous regardez le résultat de l'exécution de l'implémentation du proxy dynamique, vous pouvez obtenir le même résultat d'exécution que "2-2. Définition de la classe du proxy", il est donc facile de comprendre que le proxy dynamique fonctionne comme un proxy. Je vais. Mais pourquoi appeler cela "** dynamique **"?

Pour comprendre la nature «dynamique» d'un proxy dynamique, modifiez le code qui appelle le «newPorxyInstance» du «Proxy» pour qu'il s'exécute comme suit: Vous pouvez laisser MyInvocationHandler comme l'implémentation ci-dessus.

    ArrayList<String> list = new ArrayList<String>();
    MyInvocationHandler myInvocationHandler = new MyInvocationHandler(list);
    @SuppressWarnings("unchecked")
    List<String> proxiedObj = (List<String>)Proxy.newProxyInstance(MyInterface.class.getClassLoader(),
        new Class[] { List.class },
        myInvocationHandler);
    proxiedObj.add("hoge");

C'est le résultat de l'exécution du code ci-dessus.

Résultat d'exécution


DynamicProxy:before
DynamicProxy:after

La sortie du résultat d'exécution ci-dessus montre que l'appel à proxiedObj.add (" hoge "); appelé ʻinvoke de MyInvocationHandler`.

Il convient de noter ici que l'objet Proxy de MyInterface peut être obtenu jusqu'à présent, mais le proxy de l'interface List peut également être obtenu sans changer du tout leMyInvocationHandler. Le code de «MyInvocationHandler» n'a pas de caractères «MyInterface» ou «List», et «MyInvocationHandler» ne fait en fait aucune dépendance sur ces interfaces. En spécifiant la classe d'interface du deuxième argument de newProxyInstance de Proxy, une nouvelle classe est générée ** pendant l'exécution de newProxyInstance ** inside Proxy, et l'objet Proxy est acquis. Je vais.

La relation entre le proxy dynamique et la classe implémentée est indiquée dans le diagramme de classes.

dynamicproxy_class_small.png

La classe verte est une classe préparée avant le démarrage du processus Java, tandis que la classe rouge est créée après le démarrage du processus Java et la newProxyInstance de Proxy est appelée ** (instance). Non) classe **. newProxyInstance renvoie une instance de la classe nouvellement créée (bien qu'elle soit récupérée de la carte si elle a déjà créé la même classe).

** Comment, le processus de la méthode newProxyInstance crée une classe qui n'existe pas dans le code source! ** [^ 1]

«** Static » et « Dynamic » du proxy sont des classes de proxy « static» qui sont prédéfinies et invariantes jusqu'à la fin pendant la période d'exécution du processus Java. "", la classe proxy générée à partir du milieu du processus pendant que le processus est en cours d'exécution est appelée " dynamic **".

4. Mise en œuvre d'un proxy dynamique plus pratique

Ceci est la fin de l'utilisation de Proxy.

Cependant, dans les exemples d'implémentation de proxy dynamique présentés jusqu'à présent, l'utilisateur de l'objet appelle newProxyInstance of Proxy pour obtenir l'objet Proxy comprenant l'objet cible. Cependant, cette implémentation n'est pas très pratique.

Mise en œuvre avec modèle d'usine appliqué

Par conséquent, il existe un modèle de conception GoF appelé ** Factory Pattern **, nous allons donc utiliser ce modèle pour simplifier le code acquis par l'objet Proxy. Le modèle de fabrique est une méthode d'acquisition d'un objet à partir d'une méthode d'une classe dédiée qui crée l'objet sans appeler le constructeur directement à l'utilisateur.

Par exemple

Exemple de modèle d'usine ①


    MyInterface myObj = MyFactory.getInstance();

Si l'objet Proxy peut être obtenu en appelant la méthode getInstance () de MyFactory, l'implémentation côté utilisateur de l'objet Proxy sera très simple.

Cependant, la méthode getInstance () de MyFactory ne transmet aucune information de l'appelant, donc telle quelle, elle passe toujours par le même ʻInvocationHandler et la même classe (ici, Proxy qui appelle l'objet de MyClass" Vous ne pouvez obtenir que des "objets. Si vous passez la classe" MyInterface ", l'objet" MyClass "et l'objet" InvocationHandler "comme arguments, ce sera une fabrique polyvalente. Mais si vous y réfléchissez, le ʻInvocationHandler est un proxy. Puisqu'il s'agit de la définition du processus à exécuter, la fabrique peut utiliser une classe concrète fixe ʻInvocationHandler en interne (changer la classe concrète ʻInvocationHandlerest possible en séparant la classe de fabrique et la méthode). De plus, l'objetMyClass n'a pas besoin d'être spécifié comme argument si la classe dérivée de la classe MyInterface est fixée sur MyClass`.

Par conséquent, ici, nous allons implémenter une fabrique qui acquiert l'objet Proxy en spécifiant uniquement l'interface dans l'argument de la fabrique comme indiqué ci-dessous.

Exemple de modèle d'usine ②


    MyInterface myObj = MyFactory.getInstance(MyInterface.class);

En supposant que vous implémentiez une usine comme celle ci-dessus, il y a un problème.

Puisque seule la classe d'interface est spécifiée dans cet argument, il est nécessaire d'obtenir la classe dérivée pour l'interface à l'intérieur de la fabrique et de créer un objet de cette classe dérivée. La manière la plus simple de faire cela est d'avoir une carte des classes pour les interfaces dans l'usine. Cette méthode est assez fine et pratique, mais dans un système où il existe des dizaines ou des centaines de définitions d'interface et de classe, chaque fois qu'une nouvelle interface et une nouvelle classe sont définies, elles sont en usine. Vous devez ajouter une carte, ce qui est un peu gênant et peut provoquer des conflits de source dans le développement de groupe.

Par conséquent, nous allons présenter ici comment spécifier une classe dérivée dans les informations supplémentaires de l'interface. Tout d'abord, définissez une annotation comme celle-ci:

MyAnnotation.java


/**Une annotation qui spécifie la classe d'implémentation de l'interface*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    Class<?> concretizedClass();
}

Ajoutez cette annotation à «MyInterface» que vous avez définie précédemment. Dans l'argument de l'annotation, spécifiez la classe dérivée à sélectionner en usine.

MyInterface.java


/**Ajout d'une définition de classe d'implémentation pour instancier avec annotation*/
@MyAnnotation(concretizedClass = MyClass.class)
public interface MyInterface {
    public String myMethod(String value);
}

Enfin, c'est l'implémentation de MyFactory qui est une classe d'usine. La classe concrète de ʻInvocationHandler est définie par la classe anonyme dans la méthode getInstancedeMyFactory`.

MyFactory.java


/**Une classe qui crée un objet pour l'interface spécifiée par l'argument*/
public class MyFactory {
    /**Une méthode pour obtenir un objet de la classe d'implémentation de l'interface spécifiée par l'argument interfaceClass*/
    public static <I> I getInstance(Class<I> interfaceClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException  {
        //Obtenez la classe d'implémentation pour MyInterface à partir de l'annotation MyInterface
        MyAnnotation myAnnotation = interfaceClass.getAnnotation(MyAnnotation.class);
        Class<?> objClass = myAnnotation.concretizedClass();

        //Créer un objet de classe d'implémentation
        @SuppressWarnings("unchecked")
        I obj = (I)objClass.getDeclaredConstructor().newInstance();

        //Créer un objet en définissant InvocationHandler avec une classe anonyme
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("DynamicProxy:before");
                Object result = method.invoke(obj, args);
                System.out.println("DynamicProxy:after");
                return result;
            }
        };

        //Créer un objet proxy
        @SuppressWarnings("unchecked")
        I proxiedObj = (I)Proxy.newProxyInstance(interfaceClass.getClassLoader(),
            new Class[] { interfaceClass },
            invocationHandler);
        return proxiedObj;
    }
}

C'est le résultat de l'implémentation et de l'exécution du côté utilisateur MyFactory ci-dessus.

    MyInterface myObj = MyFactory.getInstance(MyInterface.class);
    System.out.println(myObj.myMethod("Argument"));

Résultat d'exécution


DynamicProxy:before
Method:Argument
DynamicProxy:after
Result

Pour rendre le code plus pratique

Avec les améliorations ci-dessus, nous sommes plus proches du code "utilisable" réel, mais il y a encore de la place pour des améliorations et des problèmes qui doivent être résolus.

Ils sont répertoriés ci-dessous pour votre référence lors de l'application réelle du proxy dynamique.

  • La méthode getInstance de MyFactory lève l'exception NoSuchMethodException qui peut se produire en interne. Les exceptions qui ne peuvent pas se produire ne doivent pas être incluses dans la clause throws
  • Pour la ʻInterface Class spécifiée dans l'argument, vérifiez que la ʻobj Class spécifiée dans l'annotation est une classe concrète de la ʻinterface Class`.
  • Il est pratique de fournir une option pour spécifier ʻobjClass pour ʻinterface Class non seulement dans l'annotation d'interface mais aussi directement dans MyFactory ou à partir du fichier de configuration.
  • ʻInterface Class` est plus cool à spécifier de manière générique que de le spécifier comme argument de méthode
  • ʻObject result = method.invoke (obj, args); Vous devez décider de lancer l'exception qui se produit danstelle quelle, ou de lancer l'objetThrowable qui l'a provoquée dans getCause ()`.
  • getInstance () crée un nouvel objet Proxy à chaque fois, mais si c'est un bon objet avec Sigletone, ne le créez pas à chaque fois
  • Permet aux utilisateurs de Proxy d'acquérir des objets par DI (Devedency Injection) au lieu d'utiliser directement MyFactory

Le modèle qui détermine la classe dérivée pour une telle interface et renvoie un objet approprié est appelé le ** modèle ServiceLocator **, séparément du modèle de fabrique.

5. Proxy dynamique dans d'autres langues

Jusqu'à présent, nous avons introduit le proxy dynamique par Java. Ce chapitre présente comment le proxy dynamique est fourni dans d'autres langages de programmation.

5-1.C#

(Selon cfm-art, il est dit que le standard RealProxy fournit la fonction de proxy dynamique. Ce chapitre sera à nouveau ajouté.)

~~ Il n'y a pas de classe équivalente pour le proxy dynamique dans .NET Framework, qui est une bibliothèque standard de C #. ~~ ~~ Cependant, ~~ Le Château du projet externe fournit la fonction de proxy dynamique dans la bibliothèque externe.

Nom version
.NET Framework 4.6.1
Castle.DynamicProxy2 2.2.0

DynamicProxyTest.cs


using Castle.Core.Interceptor;
using Castle.DynamicProxy;

namespace MyNamespace
{
    public interface IMyInterface
    {
        string MyMethod(int value);
    }

    public class MyClass : IMyInterface
    {
        public string MyMethod(int value)
        {
            Console.WriteLine("MyMethod:" + value);
            return "Result";
        }
    }

    public class MyInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            Console.WriteLine("MyInterceptor:before");
            object ret = invocation.Method.Invoke(invocation.InvocationTarget, invocation.Arguments);
            Console.WriteLine("MyInterceptor:after");
            invocation.ReturnValue = ret;
        }
    }
}
    ProxyGenerator generator = new ProxyGenerator();

    IMyInterface proxy = generator.CreateInterfaceProxyWithTargetInterface(
        typeof(IMyInterface),
        new MyClass(),
        new MyInterceptor()) as IMyInterface;

    Console.WriteLine(proxy.MyMethod(1));

Résultat d'exécution


MyInterceptor:before
MyMethod:1
MyInterceptor:after
Result

Comme vous pouvez le voir en regardant les méthodes définies dans ProxyGenerator, il existe de nombreuses fonctions fournies par Proxy de Java. En Java, la classe Proxy ne peut être créée qu'avec une classe concrète de l'interface, mais avec le C # ProxyGenerator, vous pouvez créer un proxy dynamique pour la classe dérivée de la classe en plus de l'interface.

5-2.Python Python n'a pas non plus d'équivalent au proxy dynamique. Cependant, des méthodes spéciales standard peuvent être utilisées pour obtenir une fonctionnalité équivalente à un proxy dynamique.

Nom version
Python 3.7.1

dynamicProxyTest.py


class MyProxy(object):
    def __init__(self, obj):
        self._obj = obj

    def __getattr__(self, name):
        def func(*args):
            print(before)
            result = getattr(self._obj, name)(*args)
            print('after')
            return result
        return func

class MyClass(object):
    def myFunction(self, arg):
        print('myFunction ' + arg)
        return 'result'

myObj = MyClass()
myProxy = MyProxy(myObj)
print(myProxy.myFunction('arg'))

Résultat d'exécution


before
myFunction arg
after
result

Cette classe MyProxy est une brève explication pour ceux qui ne sont pas familiers avec la syntaxe Python.

En Python, il existe une méthode spéciale (méthode spéciale) d'une méthode avec un nom spécifique avec "\ _ \ _" avant et après la définition de classe. Les méthodes spéciales sont appelées par certains événements qui se produisent sur l'objet, tels que la création d'un objet, l'appel d'un attribut ou la spécification par la valeur d'un opérateur particulier.

La méthode __getattr__ est appelée lorsque vous essayez d'obtenir un attribut qui n'est pas défini dans un objet de cette classe. L'utilisateur de l'objet obtient la valeur de retour de «getattr» comme valeur d'attribut si l'attribut avec le nom spécifié dans la classe n'est pas défini.

En Python, toutes les variables et méthodes d'une classe sont des attributs. La classe MyProxy définit seulement deux méthodes spéciales, la méthode __init __ et la méthode __ getattr__, elle essaie donc d'obtenir des attributs d'un objet de cette classe (c'est-à-dire qu'elle essaie d'appeler une méthode). Et la méthode __getattr__ est toujours appelée. Par conséquent, pour que MyProxy se comporte de la même manière qu'un proxy dynamique, la méthode __getattr__ définit en interne une fonction appelée func qui agit comme un proxy et renvoie cette fonction.

L'implémentation de l'appelant est une ligne,

myProxy.myFunction('hoge')

Cependant, cela est différent de l'appel de méthode Java.

  1. Récupérez l'attribut nommé «myFunction» à partir de l'objet «myProxy».
  2. Exécutez la valeur d'attribut obtenue en 1. avec ('hoge').

Doit être interprété comme. J'ai essayé d'obtenir l'attribut nommé «myFunction» à partir de l'objet «myProxy», mais comme «myProxy» n'avait pas l'attribut nommé «myFunction», j'obtiens la valeur de retour par la méthode «getattr» et je l'exécute.

5-3.EcmaScript(JavaScript) EcmaScript fournit une classe appelée Proxy à partir de 6. Cependant, il est utilisé différemment du Proxy de Java et agit comme un gestionnaire d'événements pour l'objet.

Ici, nous allons introduire une fonction appelée ʻapply, qui est l'un des gestionnaires d'événements dans Proxy. La fonction ʻapply est un gestionnaire d'événements qui est appelé lorsque la fonction est appelée.

Nom version
node.js v6.11.3

dynamicProxyTest.js


function myFunction(arg1, arg2) {
    console.log("myFunction:" + arg1 + ", " + arg2);
    return "result";
}

const handler = {
    apply : function (target, thisArg, argumentsList) {
        console.log("before");
        var result = target(...argumentsList);
        console.log("after");
        return result;
    }
}

let proxy = new Proxy(myFunction, handler);
console.log(proxy("hoge", "hage"));

Résultat d'exécution


before
myFunction:hoge, hage
after
result

6. Anti-modèle de proxy dynamique

Si vous lisez jusqu'ici, vous devriez pouvoir l'implémenter avec un proxy dynamique. Cependant, en raison de son degré élevé de liberté et de ses caractéristiques spéciales, le proxy dynamique est également une technologie qui, si elle est mal utilisée, peut contenir des problèmes non fonctionnels même si elle répond aux exigences fonctionnelles. Les langages introduits dans «5. Dynamic Proxy in Other Languages» sont des langages de programmation majeurs qui sont largement utilisés au moment de la rédaction de cet article (décembre 2018), mais ce sont tous des bibliothèques standard. Il est à noter qu'il ne fournit pas de proxy dynamique. Pour le dire autrement, l'utilisation du proxy dynamique lui-même est un anti-pattern. [^ 2]

Ici, les fonctionnalités du proxy dynamique sont résumées ci-dessous.

  1. Générez une classe de code source inexistante lors de l'exécution d'un processus dynamique.
  2. Séparez complètement le traitement par proxy dynamique du traitement par proxy.
  3. Le proxy dynamique peut servir de médiateur indépendamment de l'interface ou de la classe qui intervient.

Parmi ces caractéristiques, «2. Séparer complètement le traitement du proxy dynamique du traitement médiatisé par le proxy» est à la fois un avantage et un inconvénient du proxy dynamique. Les développeurs de classes qui implémentent des objets à médiation par proxy dynamique peuvent ne pas être conscients du fait que leurs classes sont appelées par médiation de proxy dynamique dans l'environnement dans lequel l'application s'exécute réellement. Cependant, en supposant que la médiation de proxy dynamique est essentielle pour que la classe fonctionne, à l'intérieur du proxy pour réutiliser la classe que vous avez créée dans un autre environnement ou pour l'exécuter dans votre propre environnement pour les tests. La difficulté augmentera car vous devez reproduire le processus de. Cela peut également rendre difficile la résolution des erreurs et des échecs.

L'alternative la plus simple au proxy dynamique consiste à utiliser une expression lambda pour définir le proxy comme suit:

    MyInterface myObj = new MyClass();
    MyProxy.invoke(() -> { nyObj.myMethod(); });

Dans la méthode MyProxy ʻinvoke, vous pouvez implémenter le traitement proxy et appeler l'objet Runnablepassé comme argument pour appeler le traitement d'origine qui a été médié. Cette méthode vous permet également d'implémenter un proxy entre les interfaces et les classes. De plus, les développeurs peuvent voir quel type de médiation la classe qu'ils définissent est appelée en regardant l'implémentationMyProxy`.

Sur la base de ce qui précède, je vais donner un exemple d'utilisation qui semble être un anti-pattern de la méthode d'application du proxy dynamique. Cependant, ces modèles ne sont pas nécessairement des modèles qui ne devraient pas être utilisés. Est-il vraiment préférable d'appliquer un proxy dynamique? Existe-t-il d'autres alternatives plus efficaces? C'est un anti-pattern dans le sens où il vaut mieux considérer. La signification originale d'un proxy est un agent. Par conséquent, j'introduirai l'anti-motif ci-dessous sous le titre «agent de ○○».

6-1. Agent travaillant dans un coin

Par exemple, dans le processus de vérification de la valeur d'entrée dans la méthode de récupération des informations dans la classe définie pour gérer des informations limitées telles que les utilisateurs dans un énorme système Web, il est dynamique. L'application d'un proxy peut ne pas être très souhaitable. Même si la normalisation et la normalisation du traitement sont nécessaires en raison du grand nombre d'éléments de valeur d'entrée, il existe d'autres moyens de normaliser certains des nombreux traitements différents autres que le proxy dynamique.

Le proxy dynamique a pour effet secondaire de rendre difficile la lecture de l'ensemble du processus à partir du code car il sépare complètement le processus. Même si l'échelle de traitement dans une petite zone devient grande, il serait souhaitable de la mettre en œuvre de manière à ce que l'ensemble puisse être saisi.

6-2. Agent secret

Supposons que le contenu de la classe concrète de ʻInvocationHandler utilisée par Proxy` ne soit pas exposé, mais que le développeur soit obligé d'implémenter une classe qui ne peut pas être exécutée sans passer par le traitement du proxy dynamique. Il peut être stressant pour les développeurs de garder secrets les prérequis pour les implémentations qu'ils développent. Bien sûr, si le système rencontre des problèmes causés par le traitement de proxy dynamique, ainsi que des problèmes psychologiques, les développeurs peuvent passer beaucoup de temps à les étudier. En outre, il peut être difficile de réutiliser le traitement mis en œuvre.

6-3. Agent avec trop de travail

C'est un cas où la responsabilité du proxy dynamique devient ambiguë. Initialement, le proxy dynamique n'était implémenté que pour les opérations non système telles que la sortie de journal et le traitement d'initialisation / terminaison lié à la base de données, mais le traitement d'analyse du corps de la requête HTTP est inclus et un traitement de pré-vérification est effectué. Il s'agit d'un cas où la responsabilité du proxy dynamique augmente à mesure que le processus d'approbation est engagé.

Il ne s'agit pas seulement de procurations dynamiques, mais les responsabilités de classe doivent être clarifiées. Vous devez également suivre cette politique lorsque vous effectuez des corrections. Si nécessaire, vous devez passer par plusieurs proxy dynamiques avec des responsabilités différentes.

6-4. Inondation d'agents

Dans le cas où le système a une structure multicouche telle que la couche contrôleur, la couche service, la couche logique et la couche d'accès aux données, ou dans un système dans lequel les objets dépendent les uns des autres de manière compliquée, tous les objets agissent comme des agents. Dans cette situation, vous pouvez avoir besoin d'un proxy pour le traitement, mais vous pouvez imaginer qu'il existe de nombreux proxy inutiles. Vous ne devez négocier que ce dont vous avez besoin et déterminer attentivement si vous avez vraiment besoin d'autres médiations par procuration.

6-5. De type agent

Comme beaucoup d'entre vous l'ont peut-être remarqué en lisant l'exemple d'implémentation de la classe concrète de ʻInvocationHandler, Proxy n'applique pas l'implémentation du modèle de proxy. Dans la méthode ʻinvoke de ʻInvocationHandler, il appartient entièrement à l'implémenteur de la classe concrète ʻInvocationHandler d'appeler réellement la méthode passée en argument. Par conséquent, «Proxy» peut être appliqué à des modèles autres que proxy.

Par exemple, DTO (Data Transfer Object) est un modèle qui définit les membres de classe et leurs méthodes set et get correspondantes. Ceci est dû au fait que la définition DTO n'est que l'interface qui déclare la méthode set et la méthode get, et l'implémentation interne est définie par ʻInvocation Hadler`.

MyDtoInvocationHadler.java


public class MyDtoInvocationHandler implements InvocationHandler {
    private Map<String, Object> map = new HashMap<String, Object>();

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //Traitement des méthodes commençant par get
        if (method.getName().startsWith("get")) {
            return map.get(method.getName().substring(3));
        }
        //Traitement des méthodes commençant par set
        else if (method.getName().startsWith("set")) {
            //réduction
        }
    }
}

Il est peu probable que cette application soit vraiment utile. Même si vous pouvez omettre l'implémentation de la classe DTO, vous devez tout de même définir l'interface et vous devez définir la méthode set et la méthode get pour chaque élément de cette interface. Dans la plupart des projets de développement, l'implémentation de la classe Entity correspondant à la classe DTO et à la table DB aura un outil qui sort automatiquement le code du document de conception, etc. au lieu de l'écriture manuscrite. Dans ce cas, peu importe que le code de sortie soit une classe ou une interface. Inversement, cela prive les développeurs de la possibilité d'incorporer des implémentations autres que de simples interactions de membres de classe dans un DTO particulier.

6-6. Agent avec de nombreuses commandes

Ce n'est pas un restaurant. Agent qui nécessite une grande quantité d'informations dans les annotations d'interface et les fichiers de configuration pour assurer la médiation de l'agent. Cela a aussi tendance à se produire lorsque vous essayez de faire effectuer à Proxy un traitement autre que proxy, comme dans" 6-5. Comme un agent ". Si vous passez une commande pour une grande quantité d'informations auprès d'un utilisateur d'agent, ne devrait-elle pas être mise en œuvre par l'utilisateur au lieu de l'agent?

7. Enfin

C'est tout pour l'explication du proxy dynamique dans cet article. Pour des explications plus détaillées, veuillez lire le site de référence ci-dessous.

Comprendre le proxy dynamique n'est pas du tout difficile, mais comme je l'ai mentionné dans "1. Introduction", d'après mon expérience personnelle, je pense que les programmeurs Java généraux de proxy dynamique ne sont pas très connus. Je vais. Nous pensons que cela est dû aux raisons suivantes.

  • La littérature pour les débutants n'introduit pas de proxy dynamique.
  • Le nom "proxy" est facilement confondu avec "proxy" côté serveur.
  • Il existe peu de situations où un proxy dynamique est requis dans le développement Java général. (En premier lieu, il n'y a pas de fonction qui ne peut être réalisée que par proxy dynamique)

Cependant, si vous êtes un programmeur qui a travaillé sur certains projets de développement Java, lorsque vous exécutez votre code sur le framework, la trace de la pile montre un appel de classe inconnu. Vous êtes-vous déjà senti étrange en voyant? Pour répondre à ces questions, il est judicieux de comprendre le proxy dynamique. De plus, je ne pense pas que la connaissance du proxy dynamique de Java soit gaspillée lors de l'apprentissage d'autres langages de programmation à partir de Java.

D'un autre côté, alors que la sortie de J2SE 1.3 incluant Proxy était en mai 2000, il est hors de propos que Martin Fowler ait proposé POJO (Plane Old Java Object) en septembre 2000. Cependant, je n'ai pas pu le gérer dans cet article en raison d'un manque de préparation.

Nous espérons que cet article vous donnera l'occasion d'acquérir des connaissances sur le proxy dynamique.

Site de référence

https://docs.oracle.com/javase/jp/10/docs/api/java/lang/reflect/Proxy.html https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html https://www.ibm.com/developerworks/jp/java/library/j-jtp08305/

[^ 1]: Strictement parlant, la documentation Java officielle affichée sur le site de référence ci-dessus dit seulement «parce que le code de la classe proxy est généré par un code système de confiance», dans la méthode «newProxyInstnce». Ne spécifie pas qu'il générera une nouvelle classe. Cela dépend vraiment de l'implémentation de la VM, et il est possible que vous implémentiez un proxy dynamique avec une approche de type Python. Cependant, la classe Proxy ne sait pas quel type de classe proxy dynamique est requis tant que la classe d'interface n'est pas spécifiée dans l'argument de la méthode newProxyInstnce, elle est donc interprétée comme créant une nouvelle classe dans la méthode newProxyInstnce. Je suis. [^ 2]: C'est un argument extrême, mais l'utilisation du proxy dynamique doit toujours être considérée avec suffisamment de soin pour soupçonner qu'il peut tomber dans un anti-pattern.

Recommended Posts