Maison > Java > javaDidacticiel > le corps du texte

Les deux proxys dynamiques de Java : les types de proxy générés par jdk et cglib et comment les implémenter

php是最好的语言
Libérer: 2018-07-28 15:44:44
original
3472 Les gens l'ont consulté

Quels sont les deux types de proxys dynamiques en Java ? Il s'agit du proxy jdk et de cglib. Aujourd'hui, nous allons discuter des deux proxy dynamiques en Java. À quoi devrait ressembler la classe proxy finale générée et comment implémenter le proxy ? apache php mysql

Les amis qui ont participé à des entretiens Java savent peut-être que les intervieweurs aiment poser des questions telles que la façon dont Spring AOP est implémenté, j'ai donc trié les questions et écrit cet article. Les concepts d'AOP et de mode proxy ne seront pas expliqués en détail ici. Parlons simplement du sujet directement, c'est-à-dire de la méthode d'implémentation d'AOP : Dynamic Proxy. Par rapport aux proxys statiques, les proxys dynamiques génèrent dynamiquement des classes proxy Java au moment de l'exécution, et les classes proxy complètent l'encapsulation de méthodes spécifiques et réalisent les fonctions d'AOP.

Voici mes arrangements et mes réflexions personnels. Les résultats ne sont peut-être pas les mêmes que ceux produits par Real JDK et Cglib, mais ils sont fondamentalement les mêmes.

À la fin de l'article, j'expliquerai également comment implémenter moi-même un proxy dynamique simple et fournirai une version simple de ma propre implémentation, bien sûr, à titre de référence uniquement.

1. Proxy JDK

Il s'agit de la méthode de proxy dynamique fournie par le package de réflexion Java java.lang.reflect Cette méthode de proxy est entièrement basée sur les interfaces. Voici d’abord un exemple simple.

Définir l'interface :

interface ifc {
  int add(int, int);
}
Copier après la connexion

Ensuite la classe d'implémentation ifc de l'interface Real :

class Real implements ifc {
  @Override
  public int add(int x, int y) {
    return x + y;
  }
Copier après la connexion

Real est la classe dont nous avons besoin vers un proxy, par exemple, nous voulons imprimer des journaux avant et après l'appel de add, qui est en fait AOP. Nous devons enfin générer une classe proxy qui implémente la même interface ifc et exécute les fonctions de Real.add, mais doit ajouter une nouvelle ligne d'instructions d'impression. Tout cela est transparent pour les utilisateurs, qui n'ont qu'à se soucier des appels d'interface. Afin d'ajouter du code supplémentaire autour de Real.add, des proxys dynamiques sont implémentés via quelque chose comme un intercepteur de méthode Dans Java Proxy, c'est 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;
  }
}
Copier après la connexion

Le plus critique. La chose ici est la méthode invoke. En fait, la méthode add de la classe proxy, ainsi que les autres méthodes (si l'interface définit également d'autres méthodes), n'appelleront finalement que la méthode Handler de ce <🎜. > , c'est à vous de définir spécifiquement ce qu'il faut faire en invoke, qui consiste généralement à appeler la méthode de la classe d'entité réelle invoke, la voici Real, et les comportements AOP supplémentaires (impression AVANT et APRÈS ). Il est donc concevable qu'il doive y avoir une instance de add dans la classe proxy, et tous les appels de méthode d'interface seront proxy par cette instance de gestionnaire. InvocationHandler

Nous devrions donc pouvoir décrire grossièrement à quoi ressemble cette classe proxy :

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});
  }
}
Copier après la connexion
Cette version est très simple, mais elle est suffisante pour répondre à nos exigences. Observons cette classe. Tout d'abord, il ne fait aucun doute qu'elle implémente l'interface

, qui est le fondement du modèle proxy. Sa méthode ifc appelle directement la méthode add de l'instance InvocationHandler, en passant trois paramètres Le premier est le pointeur this de la classe proxy elle-même, le second est la classe de réflexion de la méthode invoke, et le troisième est la liste des paramètres. Ainsi, dans la méthode add, les utilisateurs peuvent définir librement son comportement pour implémenter AOP. Le pont pour tout cela est invoke, qui complète l'interception et le proxy de la méthode. InvocationHandler

Le mode proxy nécessite généralement que la classe proxy ait une instance de la classe réelle (la classe proxy), qui est une instance de

dans ce cas, afin que la classe proxy puisse appeler l'original Real en méthode Real. Alors où est add ? La réponse est également dans Real. Par rapport au modèle d'agence standard, il semble y avoir une couche d'imbrication supplémentaire, mais cela n'a pas d'importance tant que la chaîne d'agence peut être construite, elle répond aux exigences du modèle d'agence. InvocationHandler

Remarquez comment l'instance de réflexion

de la méthode add est initialisée ici. Nous utilisons un bloc statique mAdd pour la compléter. Elle ne sera définie qu'une seule fois et il n'y aura pas de multi-thread. problème. Bien sûr, vous pouvez également utiliser le chargement différé et d'autres méthodes, mais vous devez tenir compte de la sécurité de la concurrence. static {...}

Enfin, jetons un œil à l'utilisation spécifique de

 : La méthode JDK Proxy

Handler handler = new Handler(new Real());
ifc p = (ifc)Proxy.newProxyInstance(ifc.class.getClassLoader(),
                                    new Class[] {ifc},
                                    handler);
p.add(1, 2);
Copier après la connexion

générera dynamiquement une classe proxy et nous renverra une instance qui implémente le newProxyInstanceinterface. Cette méthode nécessite trois paramètres. Le premier ClassLoader n'est pas important ; le second est la liste des interfaces, c'est-à-dire les interfaces que cette classe proxy doit implémenter, car le proxy du JDK est entièrement basé sur les interfaces et encapsule les méthodes de l'interface à la place. de la classe Entity ; le troisième paramètre est l'instance de ifc, qui sera placée dans la classe proxy finale comme pont entre l'interception de méthode et le proxy. Notez que InvocationHandler contient ici une instance de handler, ce qui est une exigence inévitable du mode proxy comme mentionné ci-dessus. 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;
  }
}
Copier après la connexion

类似于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;
  }
}
Copier après la connexion

使用方法:

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);
}
Copier après la connexion

如果你仔细和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);
  }
}
Copier après la connexion

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

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

Object re = proxy.invokeSuper(obj, args);
Copier après la connexion

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

Object re = method.invoke(obj, args);
Copier après la connexion

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

obj.add() {
  interceptor.invoke() {
    obj.add() {
      interceptor.invoke() {
        ...
      }
    }
  }
}
Copier après la connexion

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

int super_add(int x, int y) {
  return super.add(x, y);
}
Copier après la connexion

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

小结

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

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

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

Il est à noter que le code source de la classe proxy ProxyClass que j'ai donné ci-dessus n'est que la version la plus simplifiée à titre de référence, juste pour illustrer le principe, plutôt que JDK Proxy et cglibLa classe proxy réelle générée ressemble à la logique de la classe proxy réelle est beaucoup plus compliquée, mais le principe est fondamentalement le même. De plus, comme mentionné précédemment, en fait ils ne génèrent pas de code source, mais génèrent directement le bytecode de la classe. Par exemple, cglib encapsule ASM pour générer directement les données de classe.

Comment générer une classe proxy

J'ai appris ce qu'est une classe proxy, et maintenant je vais vous présenter comment générer une classe proxy. J'ai compilé deux plans basés sur les informations :

Partie 1 Une méthode consiste à générer dynamiquement

le code source, puis à le compiler dynamiquement pour obtenir la classe. Ici, vous devez utiliser la réflexion et ajouter une série d'épissages de chaînes pour générer le code source. Ce n'est pas si difficile à faire si vous comprenez parfaitement à quoi devrait ressembler la classe proxy. Alors comment compiler dynamiquement ? Vous pouvez utiliser JOOR, qui est une bibliothèque qui encapsule ProxyClass pour vous aider à compiler facilement et dynamiquement le code source Java. J'ai essayé d'écrire une démo, qui était purement expérimentale. Et il a un problème majeur. Je ne sais pas comment modifier le chemin de classe qu'il utilise pour la compilation. Par défaut, il ne peut référencer aucune classe que vous définissez vous-même, car elles ne sont pas dans le chemin de classe compilé et la compilation ne réussira pas. C'est en fait cela qui rend ce générateur de code inutile. . . J'ai contourné de force ce problème en modifiant le javax.tools.JavaCompiler de System.setProperty pour ajouter mon chemin de classe, mais ce n'est évidemment pas une solution au problème fondamental. classpath

La deuxième méthode est plus directe, qui consiste à générer le bytecode de la classe. C'est également la méthode utilisée par

. Elle encapsule ASM, qui est une bibliothèque qui peut être utilisée pour manipuler directement les données de classe. Grâce à elle, vous pouvez générer ou modifier arbitrairement la classe de votre choix. Comprendre la machine virtuelle. Ce n'est que si vous avez une bonne compréhension du bytecode que vous pourrez maîtriser cette routine technologique relativement noire. J'ai également écrit ici une démo, qui est purement une expérience. Les enfants intéressés peuvent également l'essayer eux-mêmes. L'écriture de bytecode est assez rafraîchissante. C'est similaire à l'assemblage mais en réalité beaucoup plus simple que l'assemblage. Ce n'est pas comme un assemblage, avec des registres et des adresses mémoire, des tas et des piles, ainsi que diverses variables et adresses qui circulent. La méthode d'exécution du bytecode est très claire. Les variables sont stockées dans des tables de variables locales et la pile n'est utilisée que pour les appels de fonction, elle est donc très intuitive. cglib

Articles associés :

Explication détaillée des deux proxys dynamiques de cglib et jdk

Explication détaillée des codes de JDK et cglib

Vidéos associées :

Présentation de JDK et JRE) -Tutoriel vidéo JAVA pour débutant

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal