Vorwort
"Dynamic Agent" stammt tatsächlich aus dem Proxy -Modus im Entwurfsmodus, und im Proxy -Modus verwendet das Proxy -Objekt, um Benutzeranforderungen zu vervollständigen und den Zugriff der Benutzer auf echte Objekte zu blockieren.
Um das einfachste Beispiel zu geben, möchten wir "fq" auf ausländische Websites zugreifen, da wir nicht alle fremden IPs haben. Sie können Ihr Anfrage -Datagramm an die nicht blockierten ausländischen Hosts senden, und dann leiten Sie die Anfrage an das Ziel weiter, indem Sie den ausländischen Host konfigurieren und nach dem Empfang der Antwortnachricht an unseren inländischen Host weitergeleitet werden.
In diesem Beispiel ist ein fremder Host ein Proxy -Objekt, und die Hosts, die von der Wand fallen gelassen werden, sind echte Objekte. Wir können nicht direkt auf die realen Objekte zugreifen, können aber über einen Proxy indirekt darauf zugreifen.
Ein Vorteil des Proxy -Modus besteht darin, dass alle externen Anforderungen das Proxy -Objekt durchlaufen und das Proxy -Objekt das Recht hat, zu steuern, ob Sie wirklich auf das reale Objekt zugreifen dürfen. Wenn es eine illegale Anfrage gibt, kann das Proxy -Objekt Sie ohne Probleme mit dem realen Objekt vollständig ablehnen.
Eine der typischsten Anwendungen des Proxy -Modus ist das Feder -Framework. Der AOP von Spring verwendet eine Aspekt-orientierte Programmierung, um die tatsächliche Geschäftslogik aus zugehörigen Protokollausnahmen und anderen Informationen zu isolieren. Jedes Mal, wenn Sie die Geschäftslogik anfordern, entspricht sie einem Proxy -Objekt. Dieses Proxy -Objekt ist nicht nur die erforderlichen Berechtigungsüberprüfungen und Protokolldrucke durchzuführen.
Statischer Proxy
Es gibt zwei Hauptimplementierer des Proxy -Modells, "statischer Proxy" und "Dynamic Proxy". Der wesentliche Unterschied zwischen diesen beiden besteht darin, dass die frühere Proxy -Klasse eine manuelle Codierung durch Programmierer erfordert, während die letztere Proxy -Klasse automatisch generiert wird. Deshalb haben Sie kaum von dem Konzept des "statischen Proxy" gehört. Natürlich ist es natürlich einfacher, "dynamischer Proxy" zu verstehen.
Eine Sache, die Sie klarstellen müssen, ist, dass das Proxy -Objekt alle Methoden des realen Objekts stellt, dh das Proxy -Objekt muss mindestens den gleichen Methodennamen wie das reale Objekt für den Aufruf angeben, sodass ein Proxy -Objekt alle Methoden des realen Objekts definieren muss, einschließlich Methoden in der übergeordneten Klasse.
Schauen wir uns ein einfaches statisches Proxy -Beispiel an:
Um das Problem zu veranschaulichen, definieren wir eine Iservice -Schnittstelle und lassen unsere reale Klasse die Schnittstelle erben und implementieren, damit in unserer realen Klasse zwei Methoden vorhanden sind.
Wie sollte also eine Proxy -Klasse definiert werden, um den Proxy für echte Objekte zu vervollständigen?
Im Allgemeinen besteht die Essenz einer Proxy -Klasse darin, alle Methoden in der realen Klasse zu definieren und einige andere Operationen innerhalb der Methode hinzuzufügen und schließlich die Methode der realen Klasse aufzurufen.
Die Proxy -Klasse muss alle Methoden in der tatsächlichen Klasse vervollständigen, dh Methoden, die genau mit diesen Methoden in der realen Klasse übereinstimmen, und die Methode der realen Klasse wird indirekt in diesen Methoden bezeichnet.
Im Allgemeinen wird die Proxy -Klasse im Allgemeinen entscheiden, alle Schnittstellen und Elternklassen der realen Klasse direkt zu erben, um alle übergeordneten Methodensignaturen der realen Klasse zu erhalten, dh zum ersten Mal alle übergeordneten Methoden in Angriff zu nehmen.
Als nächstes ist der Proxy keine übergeordnete Methode in der realen Klasse. Im Beispiel hier ist die Dosiermethode die eigene Methode der realen Klasse. Unsere Proxy -Klasse muss auch eine Methode mit derselben Methodesignatur definieren, um sie zu proxy.
Auf diese Weise können alle Methoden in der realen Klasse, selbst wenn unsere Proxy -Klasse abgeschlossen ist, in Zukunft durch die Proxy -Klasse ausgezeichnet werden. So was:
public static void main (String [] args) {RealClass RealClass = new RealClass (); Proxyclass proxyClass = new Proxyclass (RealClass); proxyclass.sayhello (); Proxyclass.doService ();}Proxyclass kann als Proxy -Klasse -Objekt alle Methoden in der realen Klasse veranlassen und einige "unbedeutende" Informationen drucken, bevor diese Methoden ausgeführt werden.
Dies ist im Grunde die grundlegende Implementierungsidee des Proxy -Modells, aber der Unterschied zwischen dynamischem Proxy und dieser Art von statischer Proxy besteht darin, dass dynamischer Proxy unsere Methodendefinitionen nacheinander nicht benötigt und die virtuelle Maschine diese Methoden automatisch für Sie generiert.
JDK Dynamischer Proxy -Mechanismus
Was dynamische Proxy von statischer Proxy unterscheidet, ist, dass die Proxy -Klasse dynamischer Proxy zur Laufzeit dynamisch erstellt und gelöscht wird, wenn die virtuelle Maschine deinstalliert wird.
Wir verwenden die in dem obigen statischen Proxy verwendeten Klassen wieder, um zu sehen, wie der dynamische Proxy von JDK alle Methoden einer Instanz einer bestimmten Klasse angrenzen kann.
Definieren Sie eine Handler -Verarbeitungsklasse:
Die dynamische Proxy -API in der Hauptfunktion ruft JDK auf, um eine Proxy -Klasse -Instanz zu generieren:
Es gibt immer noch ziemlich viel Code. Lassen Sie uns Stück für Stück analysieren. Erstens implementiert RealClass Interface Iservice als unsere Proxy -Klasse und definiert seine eigene Methode intern.
Als nächstes definieren wir eine Verarbeitungsklasse, die die Schnittstelle invocationHandler erbt und ihre einzigartig deklarierte Invoke -Methode implementiert. Darüber hinaus müssen wir ein Mitgliedsfeld deklarieren, um echte Objekte zu speichern, dh Proxy -Objekte, da jede Methode, die wir Proxy haben, im Grunde auf relevanten Methoden realer Objekte basiert.
In Bezug auf die Rolle dieser Invoke -Methode und die Bedeutung verschiedener formaler Parameter werden wir eine detaillierte Analyse durchführen, wenn wir den Proxy -Quellcode widerspiegeln.
Definieren Sie schließlich unsere Verarbeitungsklasse und führen Sie im Grunde genommen dynamischen Proxy basierend auf JDK durch. Die Kernmethode ist die NewProxyinStance -Methode der Proxy -Klasse. Diese Methode hat drei Parameter. Einer ist ein Klassenlader, die zweite ist eine Sammlung aller von der Proxy -Klasse implementierten Schnittstellen, und die dritte ist unsere maßgeschneiderte Prozessorklasse.
Die virtuelle Maschine verwendet den Klassenlader, den Sie zur Laufzeit zur Verfügung stellen, lädt alle angegebenen Schnittstellenklassen in den Methodenbereich und reflektiert und liest dann die Methoden in diesen Schnittstellen und kombiniert die Prozessorklasse, um einen Proxy -Typ zu generieren.
Der letzte Satz kann etwas abstrakt sein. Wie "kombiniert ich mit Prozessorklassen, um einen Proxy -Typ zu generieren"? In dieser Hinsicht geben wir die Parameter für virtuelle Maschine an und lassen die generierte Klassendatei der Proxy -Klasse speichern.
-Dsun.misc.proxygenerator.saveGeneratedFiles = true
Wir dekompilieren diese Klassendatei über Tools von Drittanbietern, und es gibt viele Inhalte. Wir teilen die Analyse:
Zunächst ist der Name dieser Proxy -Klasse sehr zufällig. Wenn in einem Programm mehrere Proxy -Klassen generiert werden müssen, ist "$ proxy + number" ihr Klassenname.
Als nächstes werden Sie feststellen, dass diese Proxy -Klasse die Proxy -Klasse und die von uns angegebene Schnittstellen -Iservice erbt (früher, wenn mehrere Schnittstellen angegeben würden, würden hier mehrere Schnittstellen vererbt).
Anschließend werden Sie feststellen, dass dieser Konstruktor einen Parameter vom Typ aufrufen Dies ist auch einer der Gründe, warum alle Proxy -Klassen Proxy als übergeordnete Klasse verwenden müssen, nämlich das Feld des InvocationHandlers in der übergeordneten Klasse. Wir werden später wissen, dass dieses kleine Design zu einem tödlichen Nachteil von JDK-basierter dynamischer Proxy führen wird, der später eingeführt wird.
Dieser Inhalt ist auch ein relativ wichtiger Bestandteil der Proxy -Klasse. Es wird ausgeführt, wenn die virtuelle Maschine die Proxy -Klasse statisch initialisiert. Dieses große Code -Stück vervollständigt die Funktion, alle Methoden in der Schnittstelle zu reflektieren, und alle reflektierten Methoden werden entsprechend einem Feld des Methodentyps gespeichert.
Darüber hinaus spiegelt die virtuelle Maschine auch drei gemeinsame Methoden im Objekt wider, dh der Proxy -Klasse wird auch das reale Objekt, das aus dem Objekt geerbt wurde, vorgestellt.
Im letzten Teil sehen wir, dass die virtuelle Maschine alle Methoden reflektiert, die auf der Grundlage des statischen Initialisierungscodeblocks proxyiert werden sollen und Proxy -Methoden für sie generiert.
Diese Methoden sehen aus wie viel Code, aber tatsächlich sind sie nur eine Codezeile, die die während der Instanziierung gespeicherte Prozessorklasse aus dem übergeordneten Klassen -Proxy herausnimmt und seine Invoke -Methode aufruft.
Die Parameter der Methode sind im Grunde gleich. Der erste Parameter ist die aktuelle Proxy -Klasse -Instanz (sie beweist, dass es in der Vergangenheit nutzlos ist, diesen Parameter zu übergeben), der zweite Parameter ist die Methode -Methode -Instanz, und der dritte Parameter ist der formale Parametersatz der Methode. Wenn nicht, ist es null.
Schauen wir uns nun die kundenspezifische Prozessorklasse an:
Alle Proxy -Klassenmethoden rufen die Invoke -Methode der Prozessorklasse auf und übergeben die aktuelle Methode der Proxy -Klasse. Diese aufgerufene Methode kann wählen, ob die Methode normal aufgerufen oder den Methodenaufruf überspringen und sogar einige zusätzliche Dinge vor und nach der Methode aufgerufen wird.
Dies ist die Kernidee eines JDK -Dynamikproxy. Fassen wir kurz den gesamten Anrufprozess zusammen.
Erstens ist die Definition einer Prozessorklasse unerlässlich und muss mit einem realen Objekt verbunden sein, dh der Proxy -Klasse -Instanz.
Als nächstes rufen wir eine Methode der Proxy -Klasse von außen auf und über den dekompilierten Quellcode wissen wir, dass die Proxy -Klassenmethode stattdessen die Invoke -Methode des Prozessors aufruft und die Methodensignatur und den formalen Methodenparametersatz übergeben wird.
Abschließend hängt die Methode normalerweise davon ab, ob der Körperverfahren die Methodenmethode tatsächlich aufruft.
Tatsächlich ist dynamischer Proxy, der basierend auf JDK implementiert ist, fehlerhaft, und diese Mängel sind nicht einfach zu reparieren, daher ist CGLIB beliebt.
Einige Mängel und Mängel
Einzelvertretermechanismus
Ich weiß nicht, ob Sie bemerkt haben, dass die obigen Beispiele nicht verfügbar sind. Die von der virtuelle Maschine generierte Proxy -Klasse erbt die Proxy -Klasse, um ihre eigenen Prozessorklasseninstanzen öffentlich zu speichern. Was bedeutet das?
Javas einzelner Root -Erbe sagt Ihnen, dass die Proxy -Klasse keine andere Klasse mehr erben kann, sodass die Methoden in der übergeordneten Klasse der Proxy -Klasse natürlich nicht erhalten können, dh die Proxy -Klasse kann keine Methoden der übergeordneten Klasse in der realen Klasse vorstellen.
Abgesehen davon ist ein weiteres kleines Detail. Ich frage mich, ob Sie es bemerkt haben. Ich habe es so geschrieben.
Die Sayhello -Methode hier ist die implementierte Schnittstelle Iservice, während die DoService -Methode eine Methode ist, die zu RealClass 'eigen ist. Wir sehen diese Methode jedoch nicht aus der Proxy -Klasse, was bedeutet, dass diese Methode nicht proxyiert ist.
Daher ist der dynamische Proxy -Mechanismus von JDK Single und kann nur Proxie -Methoden in der Schnittstellensammlung der Proxy -Klasse proxie.
Unfreundlicher Rückgabewert
Bitte beachten Sie, dass NewProxyInstance eine Instanz der Proxy -Klasse "$ proxy0" zurückgibt, sie jedoch als Objekttyp zurückgegeben wird, und Sie können die Objektinstanz nicht zum "$ proxy0" -Typ erzwingen.
Obwohl wir wissen, dass diese Objektinstanz tatsächlich der Typ "$ proxy0" ist, existiert der Typ "$ proxy0" während der Kompilierungsperiode nicht, und der Compiler erlaubt Ihnen natürlich nicht, sie zu einem nicht existierenden Typ zu zwingen. Daher zwingt es im Allgemeinen nur eine der von dieser Proxy -Klasse implementierten Schnittstellen.
RealClass rc = new RealClass (); MyHanlder Hander = new MyHander (RC); ISVICE OBJ = (ISERVICE) Proxy.NewproxyInstance (rc.getClass (). getClassloader (), neue Klasse [] {iService.classe}, handerer); Obj.Boyh.Programmlaufausgabe:
Proxy -Beginn ...... Hallo Welt ... Proxy Ending ......
Dann kommt die Frage wieder. Wenn unsere Proxy -Klasse mehrere Schnittstellen implementiert, auf welchen Schnittstellentyp sollten Sie ihn zwingen? Unter der Annahme, dass die Proxy -Klasse die Schnittstellen A und B implementiert, dann, wenn die letzte Instanz zu A gezwungen ist, können Sie natürlich nicht alle Methoden in der Schnittstelle B aufrufen, die von der Proxy -Klasse implementiert sind, und umgekehrt.
Dies führt direkt zu einem Ergebnis. Sie müssen wissen, welche Methode sich in welcher Schnittstelle befindet. Wenn Sie es der entsprechenden Schnittstelle erzwingen, bevor Sie eine Methode aufrufen, ist sie ziemlich unfreundlich.
Das obige ist das, was wir für nicht elegant im dynamischen Proxy -Mechanismus für JDK sind. Natürlich sind seine Vorteile definitiv größer als diese Nachteile. Im nächsten Artikel werden wir eine CGGLIB Dynamic Proxy -Bibliothek einführen, die von verschiedenen Frameworks weit verbreitet ist. Die zugrunde liegende Ebene basiert auf dem Bytecode -Operation Framework ASM und basiert nicht mehr auf der Vererbung zur Implementierung, wodurch die Mängel des einzelnen Proxy von JDK perfekt gelöst werden.
Alle Codes, Bilder und Dateien im Artikel werden in der Cloud auf meinem GitHub gespeichert:
(https://github.com/singleyam/overview_java)
Sie können auch lokal herunterladen.
Zusammenfassen
Das obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Referenzwert für das Studium oder die Arbeit eines jeden hat. Wenn Sie Fragen haben, können Sie eine Nachricht zur Kommunikation überlassen. Vielen Dank für Ihre Unterstützung bei Wulin.com.