Welche zwei Arten von dynamischen Proxys gibt es in Java? Sie sind JDK-Proxy und Cglib. Heute werden wir die beiden dynamischen Proxys in Java besprechen. Wie sollte die endgültig generierte Proxy-Klasse aussehen und wie der Proxy implementiert wird? apache php mysql
Freunde, die an Java-Interviews teilgenommen haben, wissen vielleicht, dass Interviewer gerne Fragen stellen, beispielsweise zur Implementierung von Spring AOP. Deshalb habe ich die Fragen geklärt und diesen Artikel geschrieben. Die Konzepte von AOP und Proxy-Modus werden hier nicht im Detail erläutert. Lassen Sie uns direkt über das Thema sprechen, nämlich die Implementierungsmethode von AOP: Dynamic Proxy. Im Vergleich zu statischen Proxys generieren dynamische Proxys zur Laufzeit dynamisch Java-Proxy-Klassen, und die Proxy-Klassen vervollständigen die Kapselung spezifischer Methoden und realisieren die Funktionen von AOP.
Dies sind meine persönlichen Vorkehrungen und Gedanken. Die Ergebnisse sind möglicherweise nicht die gleichen wie die von echtem JDK und Cglib, aber im Prinzip sind sie dieselben.
Am Ende des Artikels werde ich auch besprechen, wie ich selbst einen einfachen dynamischen Proxy implementieren kann, und natürlich nur als Referenz eine einfache Version meiner eigenen Implementierung bereitstellen.
Dies ist die dynamische Proxy-Methode, die vom Java-Reflection-Paket bereitgestellt wird java.lang.reflect
Diese Proxy-Methode basiert vollständig auf Schnittstellen. Hier ist zunächst ein einfaches Beispiel.
Definieren Sie die Schnittstelle:
interface ifc { int add(int, int); }
Dann ist die Implementierungsklasse ifc
der Schnittstelle Real
:
class Real implements ifc { @Override public int add(int x, int y) { return x + y; }
Real
die Klasse, die wir als Proxy benötigen Wenn wir beispielsweise einige Protokolle vor und nach dem Aufruf von add
drucken möchten, handelt es sich tatsächlich um AOP. Wir müssen endlich eine Proxy-Klasse generieren, die die gleiche Schnittstelle ifc
implementiert und die Funktionen von Real.add
ausführt, aber eine neue Zeile mit Druckanweisungen hinzufügen muss. All dies ist für Benutzer transparent, die sich nur um Schnittstellenaufrufe kümmern müssen. Um zusätzlichen Code um Real.add
herum hinzuzufügen, werden dynamische Proxys durch so etwas wie einen Methoden-Interceptor implementiert. In Java Proxy ist dies InvocationHandler
.
class Handler implements InvocationHandler { private final Real real; public Handler(Real real) { this.real = real; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { System.out.println("=== BEFORE ==="); Object re = method.invoke(real, args); System.out.println("=== AFTER ==="); return re; } }
Das Wichtigste hier ist die invoke
-Methode. Tatsächlich ruft die add
-Methode der Proxy-Klasse sowie andere Methoden (sofern die Schnittstelle auch andere Methoden definiert) letztendlich nur die Handler
-Methode dieser invoke
auf. Sie definieren genau, was in invoke getan werden muss, was normalerweise darin besteht, die Methode der echten Entitätsklasse Real
aufzurufen, hier ist sie add
, und zusätzliche AOP-Verhaltensweisen (Drucken VOR und NACH). Daher ist es denkbar, dass es in der Proxy-Klasse eine Instanz von InvocationHandler
geben muss und alle Schnittstellenmethodenaufrufe von dieser Handler-Instanz als Proxy ausgeführt werden.
Wir sollten also in der Lage sein, grob zu beschreiben, wie diese Proxy-Klasse aussieht:
public ProxyClass implements ifc { private static Method mAdd; private InvocationHandler handler; static { Class clazz = Class.forName("ifc"); mAdd = clazz.getMethod("add", int.class, int.class); } @Override public int add(int x, int y) { return (Integer)handler.invoke(this, mAdd, new Object[] {x, y}); } }
Diese Version ist sehr einfach, reicht aber aus, um unsere Anforderungen zu erfüllen. Betrachten wir zunächst diese Klasse. Es besteht kein Zweifel daran, dass sie die ifc
-Schnittstelle implementiert, die die Grundlage des Proxy-Musters darstellt. Seine add
-Methode ruft direkt die InvocationHandler
-Methode der invoke
-Instanz auf und übergibt drei Parameter. Der erste ist der this-Zeiger der Proxy-Klasse selbst, der zweite ist die Reflexionsklasse der add
-Methode und Die dritte ist die Parameterliste. In der invoke
-Methode können Benutzer ihr Verhalten zur Implementierung von AOP frei definieren. Die Brücke für all dies ist InvocationHandler
, die das Abfangen und Proxyieren der Methode vervollständigt.
Der Proxy-Modus erfordert im Allgemeinen, dass die Proxy-Klasse eine Instanz der realen Klasse (der Proxy-Klasse) hat, in diesem Fall eine Instanz von Real
, damit die Proxy-Klasse das Original < aufrufen kann 🎜> in Real
Methode. Wo ist also add
? Die Antwort steht auch in Real
. Im Vergleich zum Standard-Agenturmodell scheint dies eine zusätzliche Verschachtelungsebene zu haben, aber das spielt keine Rolle. Solange die Agenturkette aufgebaut werden kann, erfüllt sie die Anforderungen des Agenturmodells. InvocationHandler
der add
-Methode hier initialisiert wird. Wir verwenden einen statischen Block mAdd
, um ihn zu vervollständigen. Er wird nur einmal festgelegt und es findet kein Multithreading statt Problem. Natürlich können Sie auch Lazy Loading und andere Methoden verwenden, Sie müssen jedoch die Sicherheit der Parallelität berücksichtigen. static {...}
: Die JDK Proxy
Handler handler = new Handler(new Real()); ifc p = (ifc)Proxy.newProxyInstance(ifc.class.getClassLoader(), new Class[] {ifc}, handler); p.add(1, 2);
generiert dynamisch eine Proxy-Klasse und gibt uns eine Instanz zurück, die newProxyInstance
implementiert Schnittstelle. Diese Methode erfordert drei Parameter. Der erste ClassLoader ist nicht wichtig; der zweite ist die Schnittstellenliste, das heißt, welche Schnittstellen diese Proxy-Klasse implementieren muss, da der Proxy des JDK vollständig auf Schnittstellen basiert und stattdessen die Methoden der Schnittstelle kapselt der Entity-Klasse; der dritte Parameter ist die Instanz von ifc
, die in der endgültigen Proxy-Klasse als Brücke zwischen Methodenabfangen und Proxy platziert wird. Beachten Sie, dass InvocationHandler
hier eine Instanz von handler
enthält, was wie oben erwähnt eine unvermeidliche Voraussetzung für den Proxy-Modus ist. Real
总结一下JDK Proxy
的原理,首先它是完全面向接口的,其实这才是符合代理模式的标准定义的。我们有两个类,被代理类Real
和需要动态生成的代理类ProxyClass
,都实现了接口ifc
。类ProxyClass
需要拦截接口ifc
上所有方法的调用,并且最终转发到实体类Real
上,这两者之间的桥梁就是方法拦截器InvocatioHandler
的invoke
方法。
上面的例子里我给出类ProxyClass
的源代码,当然实际上JDK Proxy
是不会去产生源代码的,而是直接生成类的原始数据,它具体是怎么实现我们暂时不讨论,我们目前只需要关心这个类是什么样的,以及它实现代理的原理。
这是Spring
使用的方式,与JDK Proxy
不同之处在于它不是面向接口的,而是基于类的继承。这似乎是有点违背代理模式的标准格式,不过这没有关系,所谓的代理模式只是一种思想而不是严格的规范。我们直接看它是如何使用的。
现在没有接口,我们直接有实体类:
class Real { public int add(int x, int y) { return x + y; } }
类似于InvocationHandler
,这里cglib
直接使用一个叫MethodInterceptor
的类,顾名思义。
public class Interceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("=== BEFORE ==="); Object re = proxy.invokeSuper(obj, args); System.out.println("=== AFTER ==="); return re; } }
使用方法:
public static void main(String[] args) { Enhancer eh = new Enhancer(); eh.setSuperclass(Real.class); eh.setCallback(new Interceptor()); Real r = (Real)eh.create(); int result = r.add(1, 2); }
如果你仔细和JDK Proxy
比较,会发现它们其实是类似的:
首先JDK Proxy
提供interface列表,而cglib
提供superclass供代理类继承,本质上都是一样的,就是提供这个代理类的签名,也就是对外表现为什么类型。
然后是一个方法拦截器,JDK Proxy
里是InvocationHandler
,而cglib
里一般就是MethodInterceptor
,所有被代理的方法的调用都是通过它们的invoke
方法进行转接的,AOP的逻辑也是在这一层实现。
它们不同之处上面已经说了,就在于cglib
生成的动态代理类是直接继承原始类的,所以我们这里也可以大概刻画出这个代理类长什么样子:
public ProxyClass extends Real { private static Method mAdd; private static MethodProxy mAddProxy; private MethodInterceptor interceptor; static { Class clazz = Class.forName("ifc"); mAdd = clazz.getMethod("add", int.class, int.class); // Some logic to generate mAddProxy. // ... } @Override public int add(int x, int y) { return (Integer)interceptor.invoke( this, mAdd, new Object[] {x, y}, mAddProxy); } }
因为直接继承了Real
,那自然就包含了Real
的所有public方法,都通过interceptor.invoke
进行拦截代理。这其实和上面JDK Proxy
的原理是类似的,连invoke
方法的签名都差不多,第一个参数是this指针代理类本身,第二个参数是方法的反射,第三个参数是方法调用的参数列表。唯一不同的是,这里多出一个MethodProxy
,它是做什么用的?
如果你仔细看这里invoke
方法内部的写法,当用户想调用原始类(这里是Real
)定义的方法时,它必须使用:
Object re = proxy.invokeSuper(obj, args);
这里就用到了那个MethodProxy
,那我们为什么不直接写:
Object re = method.invoke(obj, args);
答案当然是不可以,你不妨试一下,程序会进入一个无限递归调用。这里的原因恰恰就是因为代理类是继承了原始类的,obj
指向的就是代理类对象的实例,所以如果你对它使用method.invoke
,由于多态性,就会又去调用代理类的add
方法,继而又进入invoke
方法,进入一个无限递归:
obj.add() { interceptor.invoke() { obj.add() { interceptor.invoke() { ... } } } }
那我如何才能在interceptor.invoke()
里去调用基类Real
的add
方法呢?当然通常做法是super.add()
,然而这是在MethodInterceptor
的方法里,而且这里的method调用必须通过反射完成,你并不能在语法层面上做到这一点。所以cglib
封装了一个类叫MethodProxy
帮助你,这也是为什么那个方法的名字叫invokeSuper
,表明它调用的是原始基类的真正方法。它究竟是怎么办到的呢?你可以简单理解为,动态代理类里会生成这样一个方法:
int super_add(int x, int y) { return super.add(x, y); }
当然你并不知道有这么一个方法,但invokeSuper
会最终找到这个方法并调用,这都是在生成代理类时通过一系列反射的机制实现的,这里就不细展开了。
对比JDK Proxy
和cglib
动态代理的使用方法和实现上的区别,就会发现,它们本质上都差不多,都是提供两个最重要的东西:
接口列表或者基类,定义了代理类(当然也包括原始类)的签名。
一个方法拦截器,完成方法的拦截和代理,是所有调用链的桥梁。
Es sollte beachtet werden, dass der Quellcode der Proxy-Klasse ProxyClass
, den ich oben angegeben habe, nur die optimierteste Version als Referenz ist, nur um das Prinzip zu veranschaulichen, und nicht JDK Proxy
und cglib
Die tatsächlich generierte Proxy-Klasse sieht aus wie die Logik der echten Proxy-Klasse, aber das Prinzip ist im Grunde dasselbe. Darüber hinaus generieren sie, wie bereits erwähnt, tatsächlich keinen Quellcode, sondern direkt den Bytecode der Klasse. Beispielsweise kapselt cglib
ASM
, um Klassendaten direkt zu generieren.
Ich habe gelernt, was eine Proxy-Klasse ist, und jetzt werde ich vorstellen, wie man eine Proxy-Klasse generiert, basierend auf den Informationen:
Teil 1 Eine Methode besteht darin, dynamisch ProxyClass
Quellcode zu generieren und ihn dann dynamisch zu kompilieren, um die Klasse zu erhalten. Hier müssen Sie Reflektion verwenden und eine Reihe von String-Spleißen hinzufügen, um Quellcode zu generieren. Das ist gar nicht so schwierig, wenn Sie genau wissen, wie die Proxy-Klasse aussehen sollte. Wie kann man also dynamisch kompilieren? Sie können JOOR verwenden, eine Bibliothek, die javax.tools.JavaCompiler
kapselt, um Ihnen beim einfachen dynamischen Kompilieren von Java-Quellcode zu helfen. Ich habe versucht, eine Demo zu schreiben, die rein experimentell war. Und es gibt ein großes Problem. Ich weiß nicht, wie ich den für die Kompilierung verwendeten Klassenpfad ändern soll. Standardmäßig kann es nicht auf Klassen verweisen, die Sie selbst definieren, da diese nicht im kompilierten Klassenpfad enthalten sind. Dies macht diesen Codegenerator tatsächlich unbrauchbar. . . Ich habe dieses Problem gewaltsam umgangen, indem ich System.setProperty
von classpath
geändert habe, um meinen Klassenpfad hinzuzufügen, aber das ist offensichtlich keine Lösung für das grundlegende Problem.
Die zweite Methode ist direkter und besteht darin, den Bytecode der Klasse zu generieren. Dies ist auch die von cglib
verwendete Methode. Sie kapselt ASM, eine Bibliothek, mit der Sie Klassendaten direkt bearbeiten können. Dies erfordert natürlich, dass Sie dies verstehen Die virtuelle Maschine kann nur mit guten Kenntnissen des Bytecodes beherrscht werden. Ich habe hier auch eine Demo geschrieben, die ein reines Experiment ist. Interessierte Kinder können es auch selbst ausprobieren. Das Schreiben von Bytecode ist recht erfrischend. Es ähnelt der Assemblierung, ist aber tatsächlich viel einfacher als die Assemblierung. Es ist nicht wie eine Assemblierung, bei der Register und Speicheradressen, Heaps und Stacks sowie verschiedene Variablen und Adressen im Umlauf sind. Die Ausführungsmethode von Bytecode ist sehr klar. Variablen werden in lokalen Variablentabellen gespeichert und der Stapel wird nur für Funktionsaufrufe verwendet, sodass sie sehr intuitiv ist.
Verwandte Artikel:
Detaillierte Erläuterung der beiden dynamischen Proxys von cglib und jdk
Detaillierte Erläuterung der Codes von JDK und cglib
Ähnliche Videos:
Überblick über JDK und JRE)-JAVA-Einsteiger-Video-Tutorial
Das obige ist der detaillierte Inhalt vonDie beiden dynamischen Proxys von Java: die von jdk und cglib generierten Proxy-Typen und deren Implementierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!