Cet article présente principalement des informations pertinentes pour une compréhension approfondie du proxy dynamique Java. Les amis qui en ont besoin peuvent s'y référer
Pour comprendre le proxy dynamique Java, vous devez d'abord comprendre ce qu'est un proxy et être familier. avec le modèle de conception . Les amis de doivent savoir que parmi les 23 modèles de conception résumés par Gof, il existe un modèle structurel objet appelé Proxy Le proxy dans proxy dynamique fait référence à ce modèle de conception. .
À mon avis, le soi-disant mode agent et le "mode décoration" parmi les 23 modèles de conception sont la même chose. Parmi les 23 modèles de conception, ils sont considérés comme deux modèles. Il existe également des articles sur Internet sur les similitudes et les différences de ces deux modèles, mais il est en effet possible de distinguer artificiellement les deux modèles. Dans une certaine mesure, je pense que ces deux modèles ne sont pas identiques. Le modèle est exactement le même. Par conséquent, si vous apprenez le mode proxy, vous maîtriserez également le mode décoration.
Modèle de proxy
En termes simples, le modèle de proxy consiste à envelopper un objet. L'objet généré après l'empaquetage a la même liste de méthodes que l'objet d'origine, mais chaque méthode. peut être enveloppé.
StatiqueProxy
Regardons d'abord un morceau de code :
package common; public class Test { static interface Subject{ void sayHi(); void sayHello(); } static class SubjectImpl implements Subject{ @Override public void sayHi() { System.out.println("hi"); } @Override public void sayHello() { System.out.println("hello"); } } static class SubjectImplProxy implements Subject{ private Subject target; public SubjectImplProxy(Subject target) { this.target=target; } @Override public void sayHi() { System.out.print("say:"); target.sayHi(); } @Override public void sayHello() { System.out.print("say:"); target.sayHello(); } } public static void main(String[] args) { Subject subject=new SubjectImpl(); Subject subjectProxy=new SubjectImplProxy(subject); subjectProxy.sayHi(); subjectProxy.sayHello(); } }
Ce code définit d'abord un SujetInterface, qui a deux méthodes.
Ensuite, la classe SubjectImpl est définie pour implémenter l'interface Subject et implémente deux de ses méthodes. Il n'y a certainement aucun problème ici.
Définissez maintenant une autre classe SubjuectImplProxy qui implémente également l'interface Subject. Le but de cette classe SubjectImplProxy est d'encapsuler une instance de la classe SubjectImpl. Elle définit une variable target en interne pour enregistrer une instance de SubjectImpl. SubjectImplProxy implémente également les deux méthodes spécifiées par l'interface, et dans sa version d'implémentation, il appelle l'implémentation de SubjectImpl, mais ajoute sa propre logique de traitement.
Je pense que ce code n'est pas difficile à comprendre. Il enveloppe SubjectImpl pour ajouter un préfixe au contenu de sortie. Cette méthode proxy est appelée proxy statique.
Procuration dynamique
D'après la démonstration ci-dessus, il n'est pas difficile de voir les défauts du proxy statique : nos deux méthodes de SubjectImpl sont enveloppées de la même manière, mais Mais vous devez écrire deux fois la même logique d'empaquetage dans SubjectImplProxy, et si l'interface Subject ajoute de nouvelles méthodes à l'avenir, SubjectImplProxy doit également ajouter de nouvelles implémentations, bien que SubjectImplProxy puisse encapsuler toutes les méthodes de la même manière.
Ensuite, je change le proxy statique dans l'exemple ci-dessus en proxy dynamique. Jetons un coup d'œil à la différence :
package common; import java.lang.invoke.MethodHandle; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Test { static interface Subject{ void sayHi(); void sayHello(); } static class SubjectImpl implements Subject{ @Override public void sayHi() { System.out.println("hi"); } @Override public void sayHello() { System.out.println("hello"); } } static class ProxyInvocationHandler implements InvocationHandler{ private Subject target; public ProxyInvocationHandler(Subject target) { this.target=target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.print("say:"); return method.invoke(target, args); } } public static void main(String[] args) { Subject subject=new SubjectImpl(); Subject subjectProxy=(Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyInvocationHandler(subject)); subjectProxy.sayHi(); subjectProxy.sayHello(); } }
Si vous seulement. regardez la méthode principale, seule la deuxième ligne est différente du proxy statique précédent. Elle génère également un objet proxy subjectProxy, mais le code généré est différent. Le proxy statique crée directement une instance de SubjectImplProxy, tandis que le proxy dynamique appelle la méthode java.lang.reflect.Proxy.newProxyInstance() Jetons un coup d'œil au code source de cette méthode :
.
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { if (h == null) { throw new NullPointerException(); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass(loader, interfaces); //获取代理类的Class /* * Invoke its constructor with the designated invocation handler. */ try { Constructor cons = cl.getConstructor(constructorParams); //constructorParams是写死的:{ InvocationHandler.class },上边返回的代理类Class一定是extends Proxy的,而Proxy有一个参数为InvocationHandler的构造函数 return cons.newInstance(new Object[] { h }); //这里通过构造函数将我们自己定义的InvocationHandler的子类传到代理类的实例里,当我们调用代理类的任何方法时, 实际上都会调用我们定义的InvocationHandler子类重写的invoke()函数 } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } catch (IllegalAccessException e) { throw new InternalError(e.toString()); } catch (InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { throw new InternalError(e.toString()); } }
La classe ci-dessus6b3d0130bba23ae47fe2b8e8cddf0195 cl = getProxyClass(loader, interfaces); La méthode getProxyClass appelée :
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException { if (interfaces.length > 65535) { //因为在class文件中,一个类保存的接口数量是用2个字节来表示的,因此java中一个类最多可以实现65535个接口 throw new IllegalArgumentException("interface limit exceeded"); } Class<?> proxyClass = null; /* collect interface names to use as key for proxy class cache */ String[] interfaceNames = new String[interfaces.length]; // for detecting duplicates Set<Class<?>> interfaceSet = new HashSet<>(); //验证interfaces里的接口是否能被类加载器加载,是否是接口,是否有重复的 for (int i = 0; i < interfaces.length; i++) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ String interfaceName = interfaces[i].getName(); Class<?> interfaceClass = null; try { interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != interfaces[i]) { throw new IllegalArgumentException( interfaces[i] + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. */ if (interfaceSet.contains(interfaceClass)) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } interfaceSet.add(interfaceClass); interfaceNames[i] = interfaceName; } /* * Using string representations of the proxy interfaces as * keys in the proxy class cache (instead of their Class * objects) is sufficient because we require the proxy * interfaces to be resolvable by name through the supplied * class loader, and it has the advantage that using a string * representation of a class makes for an implicit weak * reference to the class. */ List<String> key = Arrays.asList(interfaceNames); //使用interfaces列表作为key缓存在cache里,也就是实现了相同interfaces的代理类只会创建加载一次 /* * Find or create the proxy class cache for the class loader. */ Map<List<String>, Object> cache; synchronized (loaderToCache) { cache = loaderToCache.get(loader); if (cache == null) { cache = new HashMap<>(); loaderToCache.put(loader, cache); } /* * This mapping will remain valid for the duration of this * method, without further synchronization, because the mapping * will only be removed if the class loader becomes unreachable. */ } /* * Look up the list of interfaces in the proxy class cache using * the key. This lookup will result in one of three possible * kinds of values: * null, if there is currently no proxy class for the list of * interfaces in the class loader, * the pendingGenerationMarker object, if a proxy class for the * list of interfaces is currently being generated, * or a weak reference to a Class object, if a proxy class for * the list of interfaces has already been generated. */ //看看缓存里有没有,如果有就直接取出来然后return,否则判断根据pendingGenerationMarker判断是否有其它线程正在生成当前的代理类, 如果有则cache.wait()等待,如果没有则创建。 synchronized (cache) { /* * Note that we need not worry about reaping the cache for * entries with cleared weak references because if a proxy class * has been garbage collected, its class loader will have been * garbage collected as well, so the entire cache will be reaped * from the loaderToCache map. */ do { Object value = cache.get(key); if (value instanceof Reference) { proxyClass = (Class<?>) ((Reference) value).get(); } if (proxyClass != null) { // proxy class already generated: return it return proxyClass; } else if (value == pendingGenerationMarker) { // proxy class being generated: wait for it try { cache.wait(); } catch (InterruptedException e) { /* * The class generation that we are waiting for should * take a small, bounded time, so we can safely ignore * thread interrupts here. */ } continue; } else { /* * No proxy class for this list of interfaces has been * generated or is being generated, so we will go and * generate it now. Mark it as pending generation. */ cache.put(key, pendingGenerationMarker); break; } } while (true); } //确认要生成的代理类所属的包,如果interfaces里所有接口都是public的,代理类所属包就是默认包; 如果有interface不是public,那么所有不是public的interface必须在一个包里否则报错。 try { String proxyPkg = null; // package to define proxy class in /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (int i = 0; i < interfaces.length; i++) { int flags = interfaces[i].getModifiers(); if (!Modifier.isPublic(flags)) { String name = interfaces[i].getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, proxyPkg = ""; // use the unnamed package } { /* * Choose a name for the proxy class to generate. */ long num; synchronized (nextUniqueNumberLock) { num = nextUniqueNumber++; } String proxyName = proxyPkg + proxyClassNamePrefix + num; //生成代理类的名字,proxyPkg是上面确定下来的代理类所在的包名,proxyClassNamePrefix是写死的字符串“$Proxy”, num是一个全局唯一的long型数字,从0开始累积,每次生成新的代理类就+1,从这里也能看出生成的动态代理类的数量不能超过Long.maxValue /* * Verify that the class loader hasn't already * defined a class with the chosen name. */ /* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); //生成一个以proxyName为类名的,实现了Interfaces里所有接口的类的字节码 try { proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); //加载生成的类 } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } // add to set of all generated proxy classes, for isProxyClass proxyClasses.put(proxyClass, null); } finally { /* * We must clean up the "pending generation" state of the proxy * class cache entry somehow. If a proxy class was successfully * generated, store it in the cache (with a weak reference); * otherwise, remove the reserved entry. In all cases, notify * all waiters on reserved entries in this cache. */ //创建成功,则将cache中该key的pendingGenerationMarker替换为实际的代理类的弱引用,否则也要清除pendingGenerationMarker标记; 不管是否成功,都要执行cache.notifyAll(),让其它要创建相同代理类并且执行了cache.wait()的线程恢复执行。 synchronized (cache) { if (proxyClass != null) { cache.put(key, new WeakReference<Class<?>>(proxyClass)); } else { cache.remove(key); } cache.notifyAll(); } } return proxyClass; //最后返回代理类Class }
À ce stade, nous avons analysé le code source Java du proxy dynamique, et maintenant l'idée est très claire :
Proxy.newProxyInstance(ClassLoaderloader,Class6b3d0130bba23ae47fe2b8e8cddf0195[] interfaces, InvocationHandler h) méthode En termes simples, les opérations suivantes sont effectuées :
1. Générer un bytecode d'une classe proxy qui implémente toutes les interfaces dans les interfaces de paramètres et hérite du Proxy, puis charge. avec le classLoader dans le paramètre This proxy class.
2. Utilisez le constructeur Proxy(InvocationHandler h) de la classe parent de la classe proxy pour créer une instance de la classe proxy et transmettez notre sous-classe InvocationHandler personnalisée.
3. Renvoyez cette instance de classe proxy, car la classe proxy que nous avons construite implémente toutes les interfaces dans les interfaces (c'est-à-dire le subject.getClass().getInterfaces() passé dans notre programme), donc le retour. La classe proxy peut être convertie en type Sujet pour appeler les méthodes définies dans l'interface.
Nous savons maintenant que le subjectProxy renvoyé par Proxy.newProxyInstance() peut être converti avec succès en type Subject pour appeler la méthode définie dans l'interface. Alors, comment l'instance de classe proxy est-elle traitée après l'appel de la méthode ? Cela nécessite de jeter un œil au code source de la classe proxy. Mais la classe proxy est chargée par le bytecode généré dynamiquement par le programme. Comment regarder le code source ? Cela n'a pas d'importance, vous pouvez ajouter System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true") à la méthode principale, afin que le fichier Class de classe proxy généré soit enregistré sur le serveur local. disque puis renvoyé Compilez pour obtenir le code source de la classe proxy :
package common; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements Test.Subject { private static Method m4; private static Method m1; private static Method m3; private static Method m0; private static Method m2; static { try { m4 = Class.forName("Test$Subject").getMethod("sayHello", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("Test$Subject").getMethod("sayHi", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); } catch (Exception e) { throw new RuntimeException(e); } } public $Proxy0(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } public final void sayHello() { try { this.h.invoke(this, m4, null); return; } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final boolean equals(Object paramObject) { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void sayHi() { try { this.h.invoke(this, m3, null); return; } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() { try { return (String)this.h.invoke(this, m2, null); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } }
我们可以看到代理类内部实现比较简单,在调用每个代理类每个方法的时候,都用反射去调h的invoke方法(也就是我们自定义的InvocationHandler的子类中重写的invoke方法),用参数传递了代理类实例、接口方法、调用参数列表,这样我们在重写的invoke方法中就可以实现对所有方法的统一包装了。
总结
动态代理相对于静态代理在使用上的优点主要是能够对一个对象的所有方法进行统一包装,而且后期被代理的类添加方法的时候动态代理类不需要改动。
缺点是要求被代理的类必须实现了接口,因为动态代理类在实现的时候继承了Proxy类,java不支持多继承,因此动态代理类只能根据接口来定义方法。
最后动态代理之所以叫做动态代理是因为java在实现动态代理的时候,动态代理类是在运行时动态生成和加载的,相对的,静态代理类和其他普通类一下,在类加载阶段就加载了。
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!