[JAVA] Lassen Sie uns DynamicProxy so einfach wie möglich erklären

1. Zuallererst

Die Java-Standardbibliothek verfügt über eine Klasse java.lang.reflect.Proxy. .. Diese Klasse wurde aus J2SE 1.3 hinzugefügt, und die bereitgestellte Funktion wird allgemein als DynamicProxy bezeichnet (im Folgenden auf Japanisch als "** Dynamic Proxy **" bezeichnet). Seit der Veröffentlichung von J2SE 1.3 im Mai 2000 wurde der dynamische Proxy schon lange veröffentlicht und ist bereits eine tote Technologie, wird jedoch immer noch von vielen Java-Frameworks und -Bibliotheken verwendet. Andererseits scheinen die allgemeinen Java-Programmierer nicht sehr bekannt zu sein.

In diesem Artikel werden für diejenigen, die die grundlegende Syntax von Java verstehen, allgemeine Proxy-Muster vorgestellt, um den dynamischen Proxy zu verstehen, die Notwendigkeit des dynamischen Proxys zu erläutern und ihn dann zu erläutern. Erklärt, wie der dynamische Proxy von Java verwendet wird. Außerdem wird gezeigt, wie ein dynamischer Proxy für andere Sprachen als Java (C #, Python, EcmaScript) implementiert wird.

Es wurde bestätigt, dass der in diesem Artikel veröffentlichte Java-Code in der folgenden Umgebung funktioniert. (Die Betriebsumgebung anderer Sprachen als Java wird jederzeit beschrieben.) Darüber hinaus werden einige für die Kompilierung erforderliche Importklauseln im veröffentlichten Java-Code weggelassen.

Name Ausführung
OpenJDK 11.0.1

2. "Statischer" Proxy (Fortsetzung)

Das Verständnis der Verwendung des dynamischen Proxys "java.lang.reflect.Proxy" sollte für Programmierer, die mit Java vertraut sind, nicht schwierig sein. Um jedoch die Notwendigkeit eines dynamischen Proxys zu verstehen und ihn effektiv zu nutzen, sollten Sie den " statischen </ b>" Proxy verstehen, der das Gegenteil von " dynamischer </ b>" ist. müssen es tun. Dies wurde in "Ich werde das Proxy-Muster so einfach wie möglich erklären" erklärt.

proxy_image_small.png

2-1. Überprüfung des statischen Proxys

In "Ich werde das Proxy-Muster so einfach wie möglich erklären", ** Eine für mehrere Klassen, die eine gemeinsame Schnittstelle verkörpern. Eingeführt bis zu ** Punkten, an denen die gemeinsame Verarbeitung mit dem Proxy von definiert werden kann. In diesem Artikel beginnen wir mit diesem Anti-Muster.

staticproxy_manyclass_small.png

2-2. Einschränkungen des statischen Proxys

Das Anwenden eines statischen Proxys weist ein Anti-Pattern auf.

Hier ist ein spezifischeres Beispiel für den oben gezeigten "Proxy, der einer Schnittstelle eine gemeinsame Verarbeitung für mehrere konkrete Klassen hinzufügt".

Eine Implementierung der Modellschicht einer Verkaufsstelle definiert drei Klassen: "** Kunde ", " Bestellung " und " Produkt **". Damit diese Klasse bei der Verarbeitung aller Methoden auf RDBMS zugreifen kann, muss eine RDBMS-Verbindung hergestellt und eine Transaktion gestartet werden, bevor die Methode ausgeführt wird. Nach der Ausführung muss die Transaktion festgeschrieben oder zurückgesetzt und die Verbindung geschlossen werden.

Alle drei Klassen sind "** add " zum Erstellen von Daten, " update " zum Aktualisieren von Daten und " search" zum Suchen von Daten. Es schien, als würde es ausreichen, wenn es eine Methode von "" und " Details abrufen **" gäbe, um detaillierte Daten zu erhalten. Daher sind diese drei Klassen konkrete Klassen der Schnittstelle "Modell". Definieren Sie dann eine Klasse namens "TransactionBarrier", die ein Proxy für die RDBMS-Verarbeitung ist, mit einer konkreten Klasse von "Model".

Es ist ein Klassendiagramm, das die Beziehung zwischen diesen Klassen zeigt.

ecmodel_proxy_class_small.png

Bis zu einem gewissen Grad hat das obige Klassendesign gut funktioniert, aber eines Tages habe ich Probleme.

"Kunden" -Informationen müssen in großen Mengen gelöscht werden, um Kundeninformationen zu schützen.

Daher wird "Kunden" eine Methode mit dem Namen "** Bulk Delete **" hinzugefügt. Diese Methode "Batch Delete" erfordert jedoch auch die Vorbearbeitung von RDBMS durch "TransactionBarrier". Fügen Sie daher der Model-Schnittstelle auch eine Methode namens" * Delete all * "hinzu.

Dann müssen Sie das "* Bulk Delete ", das der "Model" -Schnittstelle hinzugefügt wurde, zu den "Orders" und "Products" hinzufügen, für die die " Bulk Delete *" -Methode nicht erforderlich ist. In der Realität möchten wir jedoch nicht, dass "Bestellung" und "Produkt" "Alle löschen" aufrufen. Fügen Sie daher das Tag "@ veraltet" zur Methode "Alle löschen" für "Bestellung" und "Produkt" hinzu. Ich musste "UnsupportedOperationException" bedingungslos auslösen, als diese Methode aufgerufen wurde.

ecmodel_proxy_class_unsupport_small.png

Im obigen Beispiel ist es schwierig, den Ernst der Situation zu vermitteln, da in den drei Klassen nur eine spezielle Methode hinzugefügt wurde. In der tatsächlichen Systementwicklung werden jedoch je nach Projekt Dutzende bis Hunderte von Modellen definiert. .. Jedes Mal, wenn Sie in jedem Modell eine eigene Methode definieren, treten die oben genannten Fälle häufig auf, und jeder Klasse wird eine große Anzahl von Methoden hinzugefügt, die "UnsuppportedOperationException" auslösen.

Der Grund für diese Situation ist, dass * eine Gruppe von Klassen, die nicht als eine Schnittstelle implementiert werden sollten, zwangsweise von einer Schnittstelle abgeleitet wird, da wir eine gemeinsame Verarbeitung mit einem Proxy implementieren möchten *.

Ursprünglich sollten "** Kunde ", " Bestellung " und " Produkt **" separate Schnittstellen und Proxys definiert haben, wie unten gezeigt.

ideal_ecmode_proxy_small.png

Im obigen Klassendesign muss die Proxy-Klasse jedoch für jede Schnittstelle implementiert werden, was sehr problematisch ist. Erstens, obwohl es eine Eins-zu-Eins-Beziehung zwischen der Schnittstelle und der Klasse gibt, wie oben beschrieben, wird die Implementierung einige Zeit in Anspruch nehmen, wenn Sie einen Proxy definieren, der für jede dieselbe Verarbeitung implementiert, selbst wenn Sie die gemeinsame Verarbeitung direkt in der Implementierungsklasse implementieren. Selbst die Vorteile der Verwendung eines Proxys gehen verloren, da er sich nicht ändert.

Es wäre schön, wenn es einen Proxy gäbe, der in einer Implementierung für mehrere verschiedene Schnittstellen definiert werden könnte, aber die normale Java-Syntax erlaubt eine solche Implementierung nicht. Mit dynamischem Proxy ** können Sie jedoch einen gemeinsamen Proxy über mehrere Schnittstellen ** implementieren.

3. "Dynamischer" Proxy

Lassen Sie uns nun ein Implementierungsbeispiel mit dem dynamischen Proxy "java.lang.reflect.Proxy" (im Folgenden als "** Proxy ** "bezeichnet) vorstellen. </ p>

3-1. Implementierungsbeispiel der Klasse java.lang.reflect.Proxy

Definieren Sie zunächst die durch Proxy vermittelte Klasse und deren Schnittstelle. Dieser Code ist für "MyInterface" und "MyClass" in "Ich werde das Proxy-Muster so einfach wie möglich erklären". Gleich wie Implementierung.

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

Um "Proxy" zu verwenden, müssen Sie eine konkrete Klasse für die Schnittstelle "java.lang.reflect.InvocationHandler" definieren. Die konkrete Klasse von "InvocationHandler" implementiert die Methode "invoke (Object, Method, Object [])". Diese "Aufruf" -Methode ist eine Methode, die eine bestimmte Proxy-Verarbeitung implementiert.

Dies ist der Code für "MyInvocationHandler" im Implementierungsbeispiel der konkreten Klasse von "InvocationHandler". Bei der Proxy-Verarbeitung von "MyInvocationHandler" wird "MyInvocationHandler: start" als Vorverarbeitung und "MyInvocationHandler: exit" als Standardausgabe in der Nachbearbeitung ausgegeben.

MyInvocationHandler.java


import java.lang.reflect.*;

/** java.lang.reflect.InvocationHandler-Implementierungsklasse, die die in Proxy angegebene Proxy-Verarbeitung implementiert*/
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;
    }
}

Um den oben definierten "MyInvocationHandler" zu verwenden, verwenden Sie die "NewProxyInstance" -Methode von "Proxy", um das Objekt von "Proxy" abzurufen. Geben Sie das Objekt der oben definierten Klasse "MyInvocationHandler" im dritten Argument der Methode "newProxyInstance" an.

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

Dies ist das Ergebnis der Ausführung des obigen Codes.

Ausführungsergebnis


DynamicProxy:before
Method:Argument
DynamicProxy:after
Result

3-2. NewProxyInstance-Methode der Proxy-Klasse

Zuerst werde ich die newProxyInstance Methode von Proxy erklären.

Sie können das Proxy-Objekt mit newProxyInstance abrufen. Der Benutzer wandelt das erworbene Objekt vom Typ "Objekt" in einen der Typen im Klassenarray der durch das zweite Argument angegebenen Schnittstelle um und verwendet es. Der Rückgabewert dieser Methode ist vom Typ "Object", aber es ist eine abgeleitete Klasse der "Proxy" -Klasse und eine konkrete Klasse aller Schnittstellen des zweiten Arguments.

    MyInterface proxiedObj = (MyInterface)Proxy.newProxyInstance(MyClass.class.getClassLoader(),
        new Class[] { MyInterface.class },
        myInvocationHandler);
  • Geben Sie den Klassenlader im ** ersten Argument ** von newProxyInstance an. Da der Klassenlader mit der Methode "getClassLoader ()" aus allen Klassen abgerufen werden kann, kann er mit der Methode "getClassLoader ()" einer beliebigen Klasse abgerufen werden, jedoch in einer Umgebung, in der viele Klassenladeketten wie Webanwendungen verwendet werden. Es ist besser, die Klasse, die eindeutig in der Anwendung implementiert ist, so weit wie möglich anzugeben, vorausgesetzt, sie wird in verwendet. Hier wird der Klassenlader von MyClass angegeben, bei dem es sich um eine Klasse handelt, die von einem dynamischen Proxy vermittelt wird.
  • Geben Sie im ** zweiten Argument ** von newProxyInstance ein Array von Schnittstellenklassen an. In Java kann eine Klasse mehrere Schnittstellen implementieren. Geben Sie daher ein Array an, damit Sie einen dynamischen Proxy generieren können, der mehrere Schnittstellen implementiert.
  • Geben Sie im ** dritten Argument ** von newProxyInstance ein Objekt der konkreten Klasse von InvocationHandler an. Das durch dieses Argument angegebene Objekt ist der Prozess, der tatsächlich im dynamischen Proxy ausgeführt wird. Hier haben wir tatsächlich "MyInvocationHandler" implementiert, eine konkrete Klasse von "InvocationHandler", sodass dieses Objekt angegeben wird.

3-3. InvocationHandler-Aufrufmethode

Als nächstes werde ich die "invoke" -Methode der von "MyInvocationHandler" implementierten "InvocationHandler" -Schnittstelle erläutern.

Implementieren Sie die Proxy-Verarbeitung mit der Methode "invoke". Immer wenn eine Methode aufgerufen wird, die die Schnittstelle eines "Proxy" -Objekts verkörpert, wird diese "invoke" -Methode aufgerufen. Übergeben Sie die Methode "invoke" als Argument, welche Methode des Objekts "Proxy" mit welchem Argument aufgerufen wurde.

** Wenn Sie einen allgemeinen Proxy implementieren möchten **, rufen Sie die Methode des Zielobjekts in der Methode invoke auf. Die aufzurufende Methode wird als Argument der "invoke" -Methode übergeben, das aufzurufende Objekt wird jedoch von der konkreten Klasse von "InvocationHandler" selbst gehalten.

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
  • Das ** erste Argument ** von invoke wird an das Objekt der Proxy-Klasse übergeben, die diesen invoke aufgerufen hat.
  • Das ** zweite Argument ** von invoke wird an ein Objekt der Method-Klasse übergeben, das die vom Proxy-Objekt aufgerufene Methode darstellt. Beim Aufrufen wird der Wert dieses Arguments verwendet, um die Methode des tatsächlichen Objekts aufzurufen.
  • Im ** dritten Argument ** von invoke wird das in der Methode, in der das Proxy-Objekt aufgerufen wird, angegebene Argument als Object-Array übergeben. Dies verwendet auch den Wert dieses Arguments, um die Methode des tatsächlichen Objekts aufzurufen.

3-4. Was ist ein dynamischer Proxy? Was ist "dynamisch"?

Wenn Sie sich das Ergebnis der Ausführung der dynamischen Proxy-Implementierung ansehen, erhalten Sie dasselbe Ausführungsergebnis wie "2-2. Definieren der Proxy-Klasse", sodass leicht zu verstehen ist, dass der dynamische Proxy als Proxy fungiert. Ich werde. Aber warum nennt man das "** dynamisch **"?

Um die "dynamische" Natur des dynamischen Proxys zu verstehen, ändern Sie den Code, der "newPorxyInstance" von "Proxy" aufruft, wie folgt. Sie können den "MyInvocationHandler" als die oben gezeigte Implementierung belassen.

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

Dies ist das Ergebnis der Ausführung des obigen Codes.

Ausführungsergebnis


DynamicProxy:before
DynamicProxy:after

Die Ausgabe des obigen Ausführungsergebnisses zeigt, dass der "Aufruf" von "MyInvocationHandler" durch den Aufruf von "proxiedObj.add (" hoge ");" aufgerufen wurde.

Hierbei ist zu beachten, dass das, was früher das "Proxy" -Objekt von "MyInterface" abrufen konnte, auch den Proxy der "List" -Schnittstelle abrufen kann, ohne den "MyInvocationHandler" zu ändern. Der Code für "MyInvocationHandler" enthält keine "MyInterface" - oder "List" -Zeichen, und "MyInvocationHandler" macht tatsächlich keine Abhängigkeiten von diesen Schnittstellen. Durch Angabe der Schnittstellenklasse des zweiten Arguments von "newProxyInstance" von "Proxy" wird eine neue Klasse ** während der Ausführung von "newProxyInstance" ** innerhalb von "Proxy" generiert und das "Proxy" -Objekt erfasst. Ich werde.

Die Beziehung zwischen dem dynamischen Proxy und der implementierten Klasse wird im Klassendiagramm angezeigt.

dynamicproxy_class_small.png

Die grüne Klasse ist eine Klasse, die vor dem Start des Java-Prozesses vorbereitet wurde, während die rote Klasse nach dem Start des Java-Prozesses erstellt wird und die newProxyInstance von Proxy ** (Instanz) heißt. Nicht) Klasse **. newProxyInstance gibt eine Instanz der neu erstellten Klasse zurück (obwohl sie aus der Map abgerufen wird, wenn sie bereits dieselbe Klasse erstellt hat).

** Wie erstellt der Prozess in der newProxyInstance -Methode eine Klasse, die im Quellcode nicht vorhanden ist! ** [^ 1]

"** Static " und " Dynamic " des Proxys sind " statische" Proxy-Klassen, die während des Zeitraums, in dem der Java-Prozess ausgeführt wird, vordefiniert und bis zum Ende unveränderlich sind. "", die Proxy-Klasse, die aus der Mitte des Prozesses generiert wird, während der Prozess ausgeführt wird, heißt " dynamic **".

4. Implementierung eines praktischeren dynamischen Proxys

Dies ist das Ende der Verwendung von "Proxy".

In den bisher vorgestellten Beispielen für die Implementierung eines dynamischen Proxys ruft der Benutzer des Objekts jedoch "newProxyInstance" von "Proxy" auf, um das "Proxy" -Objekt einschließlich des Zielobjekts abzurufen. Diese Implementierung ist jedoch nicht sehr praktisch.

Implementierung mit angewendetem Factory-Muster

Daher gibt es ein GoF-Entwurfsmuster namens ** Factory Pattern **, daher werden wir dieses Muster verwenden, um den vom Proxy-Objekt erfassten Code zu vereinfachen. Das Factory-Muster ist eine Methode, die aus der Methode der dedizierten Klasse abgerufen werden kann, die das Objekt erstellt, ohne den Konstruktor beim Erstellen des Objekts direkt beim Benutzer aufzurufen.

Zum Beispiel

Beispiel für ein Werksmuster ①


    MyInterface myObj = MyFactory.getInstance();

Wenn das "Proxy" -Objekt durch Aufrufen der "getInstance ()" -Methode von "MyFactory" abgerufen werden kann, ist die Implementierung auf der Benutzerseite des "Proxy" -Objekts sehr einfach.

Die Methode "getInstance ()" von "MyFactory" übergibt jedoch keine Informationen vom Aufrufer. Sie durchläuft daher immer denselben "InvocationHandler" und dieselbe Klasse (hier "Proxy", der das Objekt von "MyClass" aufruft). Sie können nur "Objekte" abrufen. Wenn Sie die "MyInterface" -Klasse, das "MyClass" -Objekt und das "InvocationHandler" -Objekt als Argumente übergeben, ist dies eine vielseitige Factory. Wenn Sie jedoch darüber nachdenken, ist der "InvocationHandler" ein Proxy. Da dies die Definition des auszuführenden Prozesses ist, kann die Factory intern eine feste Betonklasse "InvocationHandler" verwenden (das Umschalten der Betonklasse "InvocationHandler" ist möglich, indem die Factory-Klasse und die Methode getrennt werden). Außerdem muss das "MyClass" -Objekt nicht als Argument angegeben werden, wenn die abgeleitete Klasse für die "MyInterface" -Klasse auf "MyClass" festgelegt ist.

Daher implementieren wir hier eine Factory, die das Proxy-Objekt abruft, indem wir nur die Schnittstelle im Argument der Factory angeben, wie unten gezeigt.

Beispiel für ein Werksmuster ②


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

Angenommen, Sie implementieren eine Factory wie die oben beschriebene, gibt es ein Problem.

Da in diesem Argument nur die Schnittstellenklasse angegeben wird, muss die abgeleitete Klasse für die Schnittstelle innerhalb der Factory abgerufen und ein Objekt dieser abgeleiteten Klasse erstellt werden. Der einfachste Weg, dies zu tun, besteht darin, eine Karte der Klassen für die Schnittstellen in der Fabrik zu haben. Diese Methode ist in Ordnung und praktisch genug, aber in einem System, in dem es Dutzende oder Hunderte von Schnittstellen- und Klassendefinitionen gibt, ist sie jedes Mal im Werk, wenn eine neue Schnittstelle und Klasse definiert wird. Sie müssen eine Karte hinzufügen, was etwas ärgerlich ist und zu Quellkonflikten bei der Gruppenentwicklung führen kann.

Daher werden wir hier vorstellen, wie eine abgeleitete Klasse in den zusätzlichen Informationen der Schnittstelle angegeben wird. Definieren Sie zunächst eine Anmerkung wie folgt:

MyAnnotation.java


/**Eine Anmerkung, die die Implementierungsklasse für die Schnittstelle angibt*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    Class<?> concretizedClass();
}

Fügen Sie diese Anmerkung zu dem zuvor definierten "MyInterface" hinzu. Geben Sie im Argument der Anmerkung die abgeleitete Klasse an, die in der Factory ausgewählt werden soll.

MyInterface.java


/**Definition der Implementierungsklasse hinzugefügt, um mit Anmerkungen zu instanziieren*/
@MyAnnotation(concretizedClass = MyClass.class)
public interface MyInterface {
    public String myMethod(String value);
}

Schließlich ist es die Implementierung von "MyFactory", einer Factory-Klasse. Die konkrete Klasse von "InvocationHandler" wird durch die anonyme Klasse in der "getInstance" -Methode von "MyFactory" definiert.

MyFactory.java


/**Eine Klasse, die ein Objekt für die durch das Argument angegebene Schnittstelle erstellt*/
public class MyFactory {
    /**Eine Methode zum Abrufen eines Objekts der Implementierungsklasse der durch das Argument interfaceClass angegebenen Schnittstelle*/
    public static <I> I getInstance(Class<I> interfaceClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException  {
        //Rufen Sie die Implementierungsklasse für MyInterface aus der MyInterface-Annotation ab
        MyAnnotation myAnnotation = interfaceClass.getAnnotation(MyAnnotation.class);
        Class<?> objClass = myAnnotation.concretizedClass();

        //Erstellen Sie ein Objekt der Implementierungsklasse
        @SuppressWarnings("unchecked")
        I obj = (I)objClass.getDeclaredConstructor().newInstance();

        //Erstellen Sie ein Objekt, indem Sie InvocationHandler mit einer anonymen Klasse definieren
        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;
            }
        };

        //Erstellen Sie ein Proxy-Objekt
        @SuppressWarnings("unchecked")
        I proxiedObj = (I)Proxy.newProxyInstance(interfaceClass.getClassLoader(),
            new Class[] { interfaceClass },
            invocationHandler);
        return proxiedObj;
    }
}

Es ist das Implementierungs- und Ausführungsergebnis der oben genannten "MyFactory" -Benutzerseite.

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

Ausführungsergebnis


DynamicProxy:before
Method:Argument
DynamicProxy:after
Result

Um den Code praktischer zu gestalten

Mit den oben genannten Verbesserungen sind wir näher an dem Code, der tatsächlich "verwendbar" ist, aber es gibt noch Raum für Verbesserungen und Probleme, die behoben werden müssen.

Sie werden unten als Referenz aufgeführt, wenn Sie den dynamischen Proxy tatsächlich anwenden.

  • Die "getInstance" -Methode von "MyFactory" löst die "NoSuchMethodException" aus, die intern auftreten kann. Ausnahmen, die tatsächlich nicht auftreten können, sollten nicht in die Throws-Klausel aufgenommen werden
  • Für die durch das Argument angegebene "interfaceClass" sollte überprüft werden, ob die durch die Annotation angegebene "objClass" eine konkrete Klasse der "interfaceClass" ist.
  • Es ist praktisch, objClass für interfaceClass nicht nur in der Schnittstellenanmerkung, sondern auch direkt in MyFactory anzugeben oder eine Option bereitzustellen, die aus der Einstellungsdatei festgelegt werden kann.
  • Es ist cooler, interfaceClass als generisch anzugeben, als es als Methodenargument anzugeben.
  • Sie sollten entscheiden, ob Sie die Ausnahme auslösen möchten, die in "Object result = method.invoke (obj, args);" wie sie ist, oder das "Throwable" -Objekt auslösen, das sie in "getCause ()" verursacht hat.
  • getInstance () erstellt jedes Mal ein neues Proxy-Objekt, aber wenn es ein gutes Objekt mit Sigletone ist, erstellen Sie es nicht jedes Mal
  • Ermöglicht Benutzern von "Proxy", Objekte per DI (Devedency Injection) abzurufen, anstatt "MyFactory" direkt zu verwenden

Das Muster, das die abgeleitete Klasse für eine solche Schnittstelle bestimmt und ein geeignetes Objekt zurückgibt, wird getrennt vom Factory-Muster als ** ServiceLocator-Muster ** bezeichnet.

5. Dynamischer Proxy in anderen Sprachen

Bisher haben wir den dynamischen Proxy von Java eingeführt. In diesem Kapitel wird erläutert, wie dynamischer Proxy in anderen Programmiersprachen bereitgestellt wird.

5-1.C#

(Laut cfm-art soll der Standard-RealProxy die dynamische Proxy-Funktion bereitstellen. Dieses Kapitel wird erneut hinzugefügt.)

~~ In .NET Framework, einer Standardbibliothek von C #, gibt es keine entsprechende Klasse für dynamischen Proxy. ~~ ~~ Das Schloss des externen Projekts bietet jedoch die dynamische Proxy-Funktion in der externen Bibliothek.

Name Ausführung
.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));

Ausführungsergebnis


MyInterceptor:before
MyMethod:1
MyInterceptor:after
Result

Wie Sie anhand der in ProxyGenerator definierten Methoden sehen können, bietet Javas Proxy viele Funktionen. In Java kann die Klasse "Proxy" nur in einer konkreten Klasse einer Schnittstelle erstellt werden. In C # "ProxyGenerator" können Sie zusätzlich zur Schnittstelle einen dynamischen Proxy für eine abgeleitete Klasse einer Klasse erstellen.

5-2.Python Python hat auch kein Äquivalent zu dynamischem Proxy. Es können jedoch spezielle Standardmethoden verwendet werden, um eine dynamische Proxy-äquivalente Funktionalität zu erreichen.

Name Ausführung
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'))

Ausführungsergebnis


before
myFunction arg
after
result

Diese "MyProxy" -Klasse ist eine kurze Erklärung für diejenigen, die mit der Python-Grammatik nicht vertraut sind.

In Python gibt es eine spezielle Methode (Special Method) einer Methode mit einem bestimmten Namen mit "\ _ \ _" vor und nach der Klassendefinition. Spezielle Methoden werden von bestimmten Ereignissen aufgerufen, die für das Objekt auftreten, z. B. das Erstellen eines Objekts, das Aufrufen eines Attributs oder die Angabe durch den Wert eines bestimmten Operators.

Die Methode __getattr__ wird aufgerufen, wenn Sie versuchen, ein Attribut abzurufen, das in einem Objekt dieser Klasse nicht definiert ist. Der Benutzer des Objekts erhält den Rückgabewert "getattr" als Attributwert, wenn das Attribut mit dem in der Klasse angegebenen Namen nicht definiert ist.

In Python sind alle Variablen und Methoden einer Klasse Attribute. Die "MyProxy" -Klasse definiert nur zwei spezielle Methoden, die "init" -Methode und die "getattr" -Methode. Daher wird versucht, Attribute von einem Objekt dieser Klasse abzurufen (dh es wird versucht, eine Methode aufzurufen). Und die Methode __getattr__ wird immer aufgerufen. Damit sich "MyProxy" wie ein dynamischer Proxy verhält, definiert die Methode "getattr" intern eine Funktion namens "func", die als Proxy fungiert und diese Funktion zurückgibt.

Die Implementierung des Anrufers besteht aus einer Zeile:

myProxy.myFunction('hoge')

Dies unterscheidet sich jedoch vom Aufruf der Java-Methode.

  1. Rufen Sie das Attribut "myFunction" aus dem Objekt "myProxy" ab.
  2. Führen Sie den in 1. erhaltenen Attributwert mit ('hoge') aus.

Sollte interpretiert werden als. Ich habe versucht, das Attribut "myFunction" aus dem "myProxy" -Objekt abzurufen, aber da "myProxy" nicht über das Attribut "myFunction" verfügte, erhalte ich den Rückgabewert mit der Methode "getattr" und führe ihn aus.

5-3.EcmaScript(JavaScript) EcmaScript bietet ab 6 eine Klasse namens "Proxy". Es wird jedoch anders als Javas "Proxy" verwendet und fungiert als Ereignishandler für das Objekt.

Hier werden wir eine Funktion namens "apply" einführen, die einer der Ereignishandler in "Proxy" ist. Die Funktion apply ist ein Ereignishandler, der beim Aufrufen der Funktion aufgerufen wird.

Name Ausführung
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"));

Ausführungsergebnis


before
myFunction:hoge, hage
after
result

6. Dynamisches Proxy-Anti-Pattern

Wenn Sie so weit gelesen haben, sollten Sie es mit einem dynamischen Proxy implementieren können. Aufgrund seines hohen Freiheitsgrades und seiner besonderen Merkmale ist der dynamische Proxy jedoch auch eine Technologie, die bei Missbrauch nicht funktionierende Probleme enthalten kann, selbst wenn sie die funktionalen Anforderungen erfüllt. Die in "5. Dynamischer Proxy in anderen Sprachen" eingeführten Sprachen sind wichtige Programmiersprachen, die zum Zeitpunkt des Schreibens dieses Artikels (Dezember 2018) weit verbreitet sind, aber alle Standardbibliotheken sind. Es ist zu beachten, dass kein dynamischer Proxy bereitgestellt wird. Um es anders herum auszudrücken, die Verwendung des dynamischen Proxys selbst ist ein Anti-Pattern. [^ 2]

Hier sind die Funktionen des dynamischen Proxys unten zusammengefasst.

  1. Generieren Sie eine nicht vorhandene Quellcodeklasse, wenn Sie einen dynamischen Prozess ausführen.
  2. Trennen Sie die dynamische Proxy-Verarbeitung vollständig von der Proxy-vermittelten Verarbeitung.
  3. Dynamischer Proxy kann unabhängig von der Schnittstelle oder Klasse vermitteln, die vermittelt.

Unter diesen Merkmalen ist "2. Die Verarbeitung des dynamischen Proxys vollständig von der durch den Proxy vermittelten Verarbeitung zu trennen" sowohl ein Vorteil als auch ein Nachteil des dynamischen Proxys. Entwickler von Klassen, die dynamische Proxy-vermittelte Objekte implementieren, sind sich möglicherweise nicht bewusst, dass ihre Klassen durch dynamische Proxy-Vermittlung in der Umgebung aufgerufen werden, in der die Anwendung tatsächlich ausgeführt wird. Angenommen, die dynamische Proxy-Vermittlung ist für die Funktion der Klasse unerlässlich, um innerhalb des Proxys die von Ihnen erstellte Klasse in einer anderen Umgebung wiederzuverwenden oder in Ihrer eigenen Umgebung zum Testen auszuführen. Die Schwierigkeit wird zunehmen, weil Sie den Prozess von reproduzieren müssen. Es kann auch schwierig sein, Fehler und Ausfälle zu beheben.

Die einfachste Alternative zum dynamischen Proxy ist die Verwendung eines Lambda-Ausdrucks, um den Proxy wie folgt zu definieren:

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

Die "invoke" -Methode von "MyProxy" kann die Verarbeitung des Proxys implementieren und das als Argument übergebene "Runnable" -Objekt aufrufen, um die ursprüngliche Verarbeitung aufzurufen, die vermittelt wurde. Mit dieser Methode können Sie auch einen Proxy über Schnittstellen und Klassen hinweg implementieren. Darüber hinaus können Entwickler anhand der Implementierung von "MyProxy" erkennen, welche Art von Zwischenverarbeitung der von ihnen definierten Klasse aufgerufen wird.

Basierend auf dem oben Gesagten werde ich ein Anwendungsbeispiel geben, das ein Anti-Muster der Anwendungsmethode des dynamischen Proxys zu sein scheint. Diese Muster sind jedoch nicht unbedingt Muster, die nicht verwendet werden sollten. Ist es wirklich am besten, einen dynamischen Proxy anzuwenden? Gibt es andere effektivere Alternativen? Es ist ein Anti-Muster in dem Sinne, dass es besser ist, es zu berücksichtigen. Die ursprüngliche Bedeutung eines Proxys ist ein Agent. Daher werde ich das unten stehende Anti-Pattern unter dem Titel "○○ 's Agent" einführen.

6-1. Agent arbeitet in einer Ecke

Wenn Sie beispielsweise den Eingabewert in der Methode zum Abrufen von Informationen in der Klasse überprüfen, die für die Verarbeitung begrenzter Informationen wie Benutzer in einem großen Websystem definiert ist, ist diese dynamisch. Das Anwenden eines Proxys ist möglicherweise nicht sehr wünschenswert. Selbst wenn aufgrund der großen Anzahl von Eingabewertelementen eine Normalisierung und Standardisierung der Verarbeitung erforderlich ist, gibt es andere Möglichkeiten, einen Teil der großen Anzahl unterschiedlicher Verarbeitungen als den dynamischen Proxy zu standardisieren.

Dynamischer Proxy hat den Nebeneffekt, dass es schwierig ist, das gesamte Bild des Prozesses aus dem Code zu lesen, da er den Prozess vollständig trennt. Selbst wenn der Umfang der Verarbeitung in einem kleinen Bereich groß wird, wäre es wünschenswert, ihn so zu implementieren, dass das Ganze erfasst werden kann.

6-2. Geheimagent

Angenommen, der Inhalt der konkreten Klasse von "InvocationHandler", die von "Proxy" verwendet wird, wird nicht verfügbar gemacht, aber der Entwickler muss eine Klasse implementieren, die nicht ausgeführt werden kann, ohne die Verarbeitung des dynamischen Proxys zu durchlaufen. Für Entwickler kann es stressig sein, die Voraussetzungen für die von ihnen entwickelten Implementierungen geheim zu halten. Wenn das System auf Probleme durch dynamische Proxy-Verarbeitung sowie auf psychologische Probleme stößt, können Entwickler natürlich viel Zeit damit verbringen, diese zu untersuchen. Es kann auch schwierig sein, die implementierte Verarbeitung wiederzuverwenden.

6-3. Agent mit zu viel Arbeit

Dies ist ein Fall, in dem die Verantwortung des dynamischen Proxys nicht mehr eindeutig ist. Ursprünglich wurde der dynamische Proxy nur für nicht systembezogene Vorgänge wie Protokollausgabe und DB-bezogene Initialisierungs- / Beendigungsverarbeitung implementiert. Die Analyse des HTTP-Anforderungshauptteils ist jedoch enthalten und die Verarbeitung vor der Prüfung wird durchgeführt. Dies ist ein Fall, in dem die Verantwortung des dynamischen Proxys mit der Eingabe des Genehmigungsprozesses zunimmt.

Hier geht es nicht nur um dynamische Proxys, sondern es sollten die Verantwortlichkeiten der Klassen geklärt werden. Sie sollten diese Richtlinie auch befolgen, wenn Sie Korrekturen vornehmen. Bei Bedarf sollten Sie mehrere dynamische Proxys mit unterschiedlichen Verantwortlichkeiten durchlaufen.

6-4. Überflutung von Agenten

In dem Fall, in dem das System eine mehrschichtige Struktur aufweist, wie z. B. die Controller-Schicht, die Service-Schicht, die Logikschicht und die Datenzugriffsschicht, oder in einem System, in dem Objekte auf komplizierte Weise voneinander abhängig sind, wirken alle Objekte als Agenten. In dieser Situation benötigen Sie möglicherweise einen Proxy für die Verarbeitung, aber Sie können sich vorstellen, dass es viele unnötige Proxys gibt. Sie sollten nur das vermitteln, was Sie benötigen, und sorgfältig prüfen, ob Sie wirklich andere Proxy-Vermittlungen benötigen.

6-5. Agentenartig

Wie viele von Ihnen vielleicht beim Lesen des Implementierungsbeispiels der konkreten Klasse von "InvocationHandler" bemerkt haben, erzwingt "Proxy" die Implementierung des Proxy-Musters nicht. Bei der "invoke" -Methode von "InvocationHandler" liegt es ganz beim Implementierer des "InvocationHandler", zu entscheiden, ob die als Argument übergebene Methode tatsächlich aufgerufen werden soll oder nicht. Daher kann "Proxy" auf andere als Proxy-Muster angewendet werden.

Beispielsweise ist DTO (Data Transfer Object) ein Muster, das Klassenmitglieder und ihre entsprechenden Set- und Get-Methoden definiert. Dies liegt daran, dass die DTO-Definition nur die Schnittstelle ist, die die set-Methode und die get-Methode deklariert, und die interne Implementierung durch "Invocation Hadler" definiert wird.

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 {
        //Verarbeitung von Methoden beginnend mit get
        if (method.getName().startsWith("get")) {
            return map.get(method.getName().substring(3));
        }
        //Verarbeitung von Methoden beginnend mit set
        else if (method.getName().startsWith("set")) {
            //Kürzung
        }
    }
}

Es ist zweifelhaft, dass diese Anwendung wirklich nützlich ist. Auch wenn Sie die Implementierung der DTO-Klasse weglassen können, müssen Sie dennoch eine Schnittstelle definieren und für jedes Element in dieser Schnittstelle eine Set-Methode und eine Get-Methode definieren. In den meisten Entwicklungsprojekten verfügt die Implementierung der Entity-Klasse, die der DTO-Klasse und der DB-Tabelle entspricht, über ein Tool, das den Code automatisch aus dem Entwurfsdokument usw. anstelle der Handschrift ausgibt. In diesem Fall spielt es keine Rolle, ob der Ausgabecode eine Klasse oder eine Schnittstelle ist. Umgekehrt wird Entwicklern die Möglichkeit genommen, andere Implementierungen als einfache Interaktionen mit Klassenmitgliedern in ein bestimmtes DTO zu integrieren.

6-6. Agent mit vielen Bestellungen

Es ist kein Restaurant. Ein Agent, der eine große Menge an Informationen in Schnittstellenanmerkungen und Konfigurationsdateien benötigt, um den Agenten zu vermitteln. Dies kann auch vorkommen, wenn Sie versuchen, "Proxy" dazu zu bringen, eine andere Verarbeitung als "Proxy" auszuführen, wie in "6-5. Agent-like". Wenn eine große Menge an Informationen von einem Agentenbenutzer bestellt wird, sollten sie nicht unabhängig vom Benutzer anstelle des Agenten implementiert werden?

7. Schließlich

Dies ist alles für die Erklärung des dynamischen Proxys in diesem Artikel. Weitere Informationen finden Sie auf der unten stehenden Referenzseite.

Das Verständnis von dynamischem Proxy ist überhaupt nicht schwierig, aber wie ich in "1. Einführung" erwähnt habe, bin ich meiner persönlichen Erfahrung nach der Meinung, dass die allgemeinen Java-Programmierer von dynamischem Proxy nicht sehr bekannt sind. Ich werde. Wir spekulieren, dass dies auf folgende Gründe zurückzuführen ist.

  • In der Literatur für Anfänger wird kein dynamischer Proxy eingeführt.
  • Der Name "Proxy" kann auf der Serverseite leicht mit "Proxy" verwechselt werden.
  • In wenigen Situationen ist in der allgemeinen Java-Entwicklung ein dynamischer Proxy erforderlich. (Erstens gibt es keine Funktion, die nur durch einen dynamischen Proxy realisiert werden kann.)

Wenn Sie jedoch ein Programmierer sind, der an einigen Java-Entwicklungsprojekten gearbeitet hat, zeigt der Stack-Trace beim Ausführen Ihres Codes auf dem Framework einen unbekannten Klassenaufruf an. Haben Sie sich jemals seltsam gefühlt, als Sie sahen? Um solche Fragen zu beantworten, ist es eine gute Idee, den dynamischen Proxy zu verstehen. Ich denke auch nicht, dass das Wissen über den dynamischen Proxy von Java verschwendet wird, wenn andere Programmiersprachen von Java gelernt werden.

Während die Veröffentlichung von J2SE 1.3 einschließlich "Proxy" im Mai 2000 erfolgte, ist es irrelevant, dass Martin Fowler im September 2000 POJO (Plane Old Java Object) vorschlug. In diesem Artikel konnte ich jedoch aufgrund mangelnder Vorbereitung nicht damit umgehen.

Wir hoffen, dass dieser Artikel Ihnen die Möglichkeit gibt, sich mit dynamischem Proxy vertraut zu machen.

Referenzseite

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]: Genau genommen heißt es in der offiziellen Java-Dokumentation auf der obigen Referenzseite nur "weil der Code für die Proxy-Klasse durch vertrauenswürdigen Systemcode generiert wird" in der Methode "newProxyInstnce". Gibt nicht an, dass eine neue Klasse generiert wird. Dies hängt wirklich von der VM-Implementierung ab, und es ist möglich, dass Sie einen dynamischen Proxy mit einem Python-ähnlichen Ansatz implementieren. Da die Klasse "Proxy" jedoch nicht weiß, welche Art von dynamischer Proxy-Klasse erforderlich ist, bis die Schnittstellenklasse im Argument der Methode "newProxyInstnce" angegeben wird, wird sie als Erstellen einer neuen Klasse in der Methode "newProxyInstnce" interpretiert. Ich bin. ]

Recommended Posts