Heim > Java > javaLernprogramm > Die beiden dynamischen Proxys von Java: die von jdk und cglib generierten Proxy-Typen und deren Implementierung

Die beiden dynamischen Proxys von Java: die von jdk und cglib generierten Proxy-Typen und deren Implementierung

php是最好的语言
Freigeben: 2018-07-28 15:44:44
Original
3501 Leute haben es durchsucht

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.

1. JDK-Proxy

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);
}
Nach dem Login kopieren

Dann ist die Implementierungsklasse ifc der Schnittstelle Real:

class Real implements ifc {
  @Override
  public int add(int x, int y) {
    return x + y;
  }
Nach dem Login kopieren

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;
  }
}
Nach dem Login kopieren

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});
  }
}
Nach dem Login kopieren

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

Beachten Sie, wie die Reflektionsinstanz

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 {...}

Abschließend werfen wir einen Blick auf die spezifische Verwendung von

: 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);
Nach dem Login kopieren

-Methode

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上,这两者之间的桥梁就是方法拦截器InvocatioHandlerinvoke方法。

上面的例子里我给出类ProxyClass的源代码,当然实际上JDK Proxy是不会去产生源代码的,而是直接生成类的原始数据,它具体是怎么实现我们暂时不讨论,我们目前只需要关心这个类是什么样的,以及它实现代理的原理。

二、cglib实现动态代理

这是Spring使用的方式,与JDK Proxy不同之处在于它不是面向接口的,而是基于类的继承。这似乎是有点违背代理模式的标准格式,不过这没有关系,所谓的代理模式只是一种思想而不是严格的规范。我们直接看它是如何使用的。

现在没有接口,我们直接有实体类:

class Real {
  public int add(int x, int y) {
    return x + y;
  }
}
Nach dem Login kopieren

类似于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;
  }
}
Nach dem Login kopieren

使用方法:

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);
}
Nach dem Login kopieren

如果你仔细和JDK Proxy比较,会发现它们其实是类似的:

  1. 首先JDK Proxy提供interface列表,而cglib提供superclass供代理类继承,本质上都是一样的,就是提供这个代理类的签名,也就是对外表现为什么类型。

  2. 然后是一个方法拦截器,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);
  }
}
Nach dem Login kopieren

因为直接继承了Real,那自然就包含了Real的所有public方法,都通过interceptor.invoke进行拦截代理。这其实和上面JDK Proxy的原理是类似的,连invoke方法的签名都差不多,第一个参数是this指针代理类本身,第二个参数是方法的反射,第三个参数是方法调用的参数列表。唯一不同的是,这里多出一个MethodProxy,它是做什么用的?

如果你仔细看这里invoke方法内部的写法,当用户想调用原始类(这里是Real)定义的方法时,它必须使用:

Object re = proxy.invokeSuper(obj, args);
Nach dem Login kopieren

这里就用到了那个MethodProxy,那我们为什么不直接写:

Object re = method.invoke(obj, args);
Nach dem Login kopieren

答案当然是不可以,你不妨试一下,程序会进入一个无限递归调用。这里的原因恰恰就是因为代理类是继承了原始类的,obj指向的就是代理类对象的实例,所以如果你对它使用method.invoke,由于多态性,就会又去调用代理类的add方法,继而又进入invoke方法,进入一个无限递归:

obj.add() {
  interceptor.invoke() {
    obj.add() {
      interceptor.invoke() {
        ...
      }
    }
  }
}
Nach dem Login kopieren

那我如何才能在interceptor.invoke()里去调用基类Realadd方法呢?当然通常做法是super.add(),然而这是在MethodInterceptor的方法里,而且这里的method调用必须通过反射完成,你并不能在语法层面上做到这一点。所以cglib封装了一个类叫MethodProxy帮助你,这也是为什么那个方法的名字叫invokeSuper,表明它调用的是原始基类的真正方法。它究竟是怎么办到的呢?你可以简单理解为,动态代理类里会生成这样一个方法:

int super_add(int x, int y) {
  return super.add(x, y);
}
Nach dem Login kopieren

当然你并不知道有这么一个方法,但invokeSuper会最终找到这个方法并调用,这都是在生成代理类时通过一系列反射的机制实现的,这里就不细展开了。

小结

对比JDK Proxycglib动态代理的使用方法和实现上的区别,就会发现,它们本质上都差不多,都是提供两个最重要的东西:

  1. 接口列表或者基类,定义了代理类(当然也包括原始类)的签名。

  2. 一个方法拦截器,完成方法的拦截和代理,是所有调用链的桥梁。

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 cglibDie 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.

So generieren Sie eine Proxy-Klasse

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!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage