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.
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); }
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; }
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; } }
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
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}); } }
, 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
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
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 {...}
: 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);
générera dynamiquement une classe proxy et nous renverra une instance qui implémente le newProxyInstance
interface. 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
上,这两者之间的桥梁就是方法拦截器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
动态代理的使用方法和实现上的区别,就会发现,它们本质上都差不多,都是提供两个最重要的东西:
接口列表或者基类,定义了代理类(当然也包括原始类)的签名。
一个方法拦截器,完成方法的拦截和代理,是所有调用链的桥梁。
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 cglib
La 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.
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
. 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
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!