Detaillierte Erklärung des Dynamischen JDK -Proxy
In diesem Artikel wird hauptsächlich die Grundprinzipien des JDK -Dynamischen Proxy eingeführt, damit jeder den JDK -Proxy tiefer verstehen kann, was er ist. Verstehen Sie das wahre Prinzip des dynamischen Proxy und dessen Generationsprozess. Wenn wir in Zukunft einen JDK -Proxy schreiben, können wir einen perfekten Proxy schreiben, ohne die Demo zu überprüfen. Nehmen wir zuerst eine einfache Demo. Der nachfolgende Analyseprozess hängt von dieser Demo ab, um einzuführen. Das Beispiel wird mit JDK 1.8 ausgeführt.
JDK Proxy HelloWorld
Paket com.yao.proxy;/** * Erstellt von Robin */Public Interface HelloWorld {void SayShello ();} Paket com.yao.proxy; import com.yao.helloworld;/** * Erstellt von Robin */Public Class HelloWorldimpl implementiert HelloWorld {public void sayhello () {System.out.print ("Hallo Welt"); }} Paket com.yao.proxy; import java.lang.reflect.invocationHandler; import Java.lang.reflect.Method;/** * Erstellt von Robin */Public Class MyInvocationHandler implementiert InvocationHandler {private Objektziel; public myInvocationHandler (Objektziel) {this.target = target; } public Object Invoke (Object Proxy, Methode Methode, Object [] args) löst Throwable {System.out.println aus ("Methode:"+ method.getName ()+ "wird aufgerufen!"); return methode.invoke (target, args); }} Paket com.yao.proxy; import com.yao.helloworld; import Java.lang.reflect.constructor; Import Java.lang.Reflect.invocationHandler; Import Java.lang.Rang.Reflect.invocationTargetException; static void main (String [] args) löst NoSuchMethodException, illegalAccessexception, InvocationTargetException, InstantiationException aus {// Es gibt zwei Möglichkeiten, hier zu schreiben. Wir verwenden eine etwas komplexe Schreibweise, die jedem hilft, mehr zu verstehen. Klasse <?> Proxyclass = proxy.getProxyClass (jdkproxyTest.class.getClassloader (), helloWorld.class); endgültiger Konstruktor <?> con = proxyclass.getConstructor (invocationHandler.class); Final InvocationHandler IH = New MyInvocationHandler (New HelloWorldimpl ()); HelloWorld helloWorld = (helloWorld) con.Newinstance (ih); helloWorld.sayhello (); // Folgendes ist eine einfachere Art des Schreibens, im Wesentlichen das gleiche wie oben/* helloWorld helloWorld = (helloWorld) Proxy. newProxyInstance (jdkproxyTest.class.getClassloader (), New Class <?> [] {helloWorld.class}, New MyInvocationHandler (new HelloWorldimpl ())); helloWorld.sayhello (); */}}Führen Sie den obigen Code aus und ein so einfacher JDK -Proxy wird implementiert.
Prozess zur Erzeugung von Agenten
Der Grund, warum wir jeden Tag JDK Dynamic Proxy nennen, ist, dass diese Proxy -Klasse von JDK für uns zur Laufzeit dynamisch generiert wird. Vor dem Erläuterung des Proxy -Erzeugungsprozesses fügen wir zunächst den Parameter -dsun.misc.proxygenerator.saveGeneratedFiles = True für den JVM -Startparameter hinzu. Seine Funktion besteht darin, uns zu helfen, die Bytecode der Proxy -Klasse zu speichern, die von JDK dynamisch auf der Festplatte generiert wurde, und uns dabei helfen, den spezifischen Inhalt des generierten Proxy anzusehen. Ich habe die Intellij -Idee verwendet, und nachdem die Proxy -Klasse generiert worden war, wurde sie direkt im Stammverzeichnis des Projekts mit dem spezifischen Paketnamen als Verzeichnisstruktur platziert .
Der Prozess der Generierung einer Proxy -Klasse enthält hauptsächlich zwei Teile:
Die GetProxyClass -Methodeeingabe der Proxy -Klasse: Sie müssen den Klassenlader und die Schnittstelle übergeben
Rufen Sie dann die GetProxyClass0 -Methode an, und die Annotation darin ist sehr klar. Wenn die Proxy -Klasse, die die aktuelle Schnittstelle implementiert, existiert, wird sie direkt aus dem Cache zurückgegeben. Wenn es nicht existiert, wird es durch ProxyClassFactory erstellt. Es ist deutlich zu sehen, dass die Anzahl der Schnittstellenschnittstellen eine Grenze gibt, die 65535 nicht überschreiten. Die spezifischen Initialisierungsinformationen von Proxyclasscache sind wie folgt:
proxyclassCache = new Weapcache <> (new KeyFactory (), New ProxyClassFactory ());
Die spezifische Logik für das Erstellen einer Proxy -Klasse wird über die Anwendenmethode der ProxyClassFactory erstellt.
Die Logik in ProxyClassFactory enthält die Erstellungslogik des Paketnamens, der Proxygenerator aufruft. generateProxyClass, um die Proxy -Klasse zu generieren, und laden Sie die Proxy -Klasse -Bytecode in die JVM.
1. Die Standard -Logik für Paketnamengename ist com.sun.proxy. Wenn die Proxy-Klasse eine nicht öffentliche Proxy-Schnittstelle ist, wird der gleiche Paketname wie die Proxy-Klassenschnittstelle verwendet. Der Standardklassenname ist $ Proxy plus ein selbstverbindlicher Ganzzahlwert.
2. Nachdem der Paketname fertig ist, wird der Proxy -Bytecode gemäß der spezifischen eingehenden Schnittstelle über Proxygenerator erstellt. generateProxyClass. Der Parameter -dsun.misc.Proxygenerator.SaveGeneratedFiles = True spielt bei dieser Methode eine Rolle. Wenn wahr, speichern Sie den Bytecode auf Festplatte. In der Proxy -Klasse sind alle Proxy -Methoden -Logik die gleichen, um die Invoke -Methode des InvocationHanders aufzurufen. Wir können später die spezifischen Proxy -Dekompilierungsergebnisse sehen.
3. Laden Sie den Bytecode in den JVM durch den übergebenen Klassenlader: DefinEClass0 (Loader, Proxyname, Proxyclassfile, 0, Proxyclassfile.length);.
private statische endgültige Klasse ProxyClassFactory implementiert bifunction <classloader, class <?> [], Klasse <? >> {// Präfix für alle Proxy -Klasse -Namen private statische String -String -ProxyClassNamePrefix = "$ proxy"; // Nächste Nummer, die für die Generierung von eindeutigen Proxy -Klasse -Namen verwendet werden soll. @Override public class <?> Uepy (classloader loader, class <?> [] Interfaces) {map <class <?>, Boolean> interfaceset = new IdentityHasMap <> (interfaces.length); für (Klasse <?> Intf: Schnittstellen) { / * * Überprüfen Sie, ob der Klasse Loader den Namen dieser * Schnittstelle zu demselben Klassenobjekt auflöst. */ Class <?> InterfaceClass = null; try {interfaceClass = class.forname (intf.getName (), false, lader); } catch (classNotFoundException e) {} if (interfaceClass! } / * * Überprüfen Sie, ob das Klassenobjekt tatsächlich eine * Schnittstelle darstellt. */ if (! interfaceClass.Isersinterface ()) {neue IllegalArgumentException (interfaceClass.getName () + "ist keine Schnittstelle"); } / * * Überprüfen Sie, ob diese Schnittstelle kein Duplikat ist. */ if (interfaceset.put (interfaceClass, boolean.true)! }} String proxypkg = null; // Paket zum Definieren der Proxy -Klasse in int accessFlags = modifier.public | Modifikator.final; / * * Notieren Sie das Paket einer nicht öffentlichen Proxy-Schnittstelle, damit die * Proxy-Klasse im selben Paket definiert wird. Stellen Sie sicher, dass * alle nicht öffentlichen Proxy-Schnittstellen im selben Paket sind. */// Paketame und Klassenname Logik für (Klasse <?> Intf: interfaces) {int flags = intf.getModifiers (); if (! modificier.ispublic (Flags)) {AccessFlags = modifier.final; String name = intf.getName (); int n = name.lastIndexof ('.'); String pkg = ((n == -1)? "": Name.substring (0, n + 1)); if (proxypkg == null) {proxypkg = pkg; } else if (! pkg.equals (proxypkg)) {werfen Sie neue illegalArgumentException ("nicht öffentliche Schnittstellen aus verschiedenen Paketen"); }}}} if (proxypkg == null) {// Wenn keine nicht öffentlichen Proxy-Schnittstellen, verwenden Sie com.sun.proxy paket proxypkg = reflectil.proxy_package + ". } / * * Wählen Sie einen Namen, damit die Proxy -Klasse generiert werden kann. */ long num = nextUniqueNumber.getandIncrement (); String proxyname = proxypkg + proxyclassnameprefix + num; / * * Generieren Sie die angegebene Proxy -Klasse. Generieren Sie den Bytecode der Proxy -Klasse* -dsun.misc.proxygenerator Versuchen Sie {// in JVM return defeclass0 (Loader, Proxyname, Proxyclassfile, 0, ProxyClassfile.length); } catch (classformaterror e) { / * * Ein klassifischer Formaterror bedeutet, dass (Bugs im * Proxy -Klassengenerierungscode) einen anderen * ungültigen Aspekt der Argumente, die der Proxy * -Klasserstellung geliefert wurden (z. B. virtuelle Maschinenbeschränkungen * überschritten wurden). */ werfen neue illegalArgumentException (e.toString ()); }}}}Wir können basierend auf der Bytecode der Proxy -Klasse dekompilieren und die folgenden Ergebnisse erzielen. HelloWorld hat nur die Sayhello -Methode, aber es gibt vier Methoden in der Proxy -Klasse, die drei Methoden zum Objekt enthalten: Gleichen, Tostring und HashCode.
Die grobe Struktur des Agenten umfasst 4 Teile:
Paket com.sun.proxy; import com.yao.helloworld; import java.lang.reflect.invocationHandler; import Java.lang.reflect.method; java.lang.reflect.proxy; Methode M1; private statische Methode M3; private statische Methode M2; private statische Methode M0; public $ proxy0 (invocationHandler var1) löst {Super (var1) aus; } public Final Boolean Equals (Object var1) löscht {try {return ((boolean) super.h.invoke (this, M1, neues Objekt [] {var1})). BooleanValue (); } catch (RunTimeException | Fehler var3) {throw var3; } catch (throwable var4) {werfen Sie neue nicht deklarierte Drüsenverfeinerung (var4); }} public Final void SayShello () löscht {try {super.h.invoke (this, m3, (Object []) null); } catch (RunTimeException | Fehler var2) {throw var2; } catch (throwable var3) {werfen Sie neue nicht deklarierte ThrowableException (var3); }} public Final String toString () löscht {try {return (string) super.h.invoke (this, m2, (Object []) null); } catch (RunTimeException | Fehler var2) {throw var2; } catch (throwable var3) {werfen Sie neue nicht deklarierte ThrowableException (var3); }} public Final int hashCode () löscht {try {return ((Integer) Super.H.Invoke (this, m0, (Object []) null)). IntValue (); } catch (RunTimeException | Fehler var2) {throw var2; } catch (throwable var3) {werfen Sie neue nicht deklarierte ThrowableException (var3); }} static {try {m1 = class.forname ("java.lang.object"). getMethod ("Equals", New Class [] {class.forname ("java.lang.Object")}); M3 = class.Forname ("com.yao.Helloworld"). GetMethod ("Sayhello", neue Klasse [0]); m2 = class.Forname ("java.lang.object"). getMethod ("tostring", neue Klasse [0]); M0 = class.Forname ("java.lang.object"). getMethod ("HashCode", neue Klasse [0]); } catch (NoSuchMethodException var2) {neue NoSuchMethoderror werfen (var2.getMessage ()); } catch (classNotFoundException var3) {neue noclassDeffoundError werfen (var3.getMessage ()); }}}FAQ:
1.. ToString () HashCode () Equal () Methode Aufruf Logik: Wenn die Methoden dieser drei Objekte aufgerufen werden, werden sie wie andere Interface -Methoden und -Methoden durch die InvocationHandler -Logik geleitet. Sie können deutlich aus den obigen Bytecode -Ergebnissen erkennen. Andere Methoden zu Objekten folgen nicht der Proxy -Verarbeitungslogik, sondern folgen direkt der von Proxy geerbten Methodenlogik für Objekte.
2. Wenn die Schnittstelle gleiche Methoden für HashCode -Methoden enthält, folgt sie wie die Logik des Aufrufs der Aufrufe wie die Handhabung gewöhnlicher Schnittstellenmethoden und löst die Methodenlogik basierend auf dem Umschreiben des Zielobjekts aus.
3. Die Schnittstelle enthält doppelte Methodensignaturen, die der Reihenfolge unterliegen, in der die Schnittstelle übergeben wird. Wer die Methode davor verwendet, behält nur eine Methode in der Proxy -Klasse bei, und es gibt keine doppelten Methodensignaturen.
Danke fürs Lesen, ich hoffe, es kann Ihnen helfen. Vielen Dank für Ihre Unterstützung für diese Seite!