1. Définition du modèle de proxy
Fournissez un objet proxy pour un objet, et l'objet proxy contrôle l'accès à l'objet d'origine, c'est-à-dire que le client ne contrôle pas directement l'objet d'origine, mais le contrôle indirectement. via l'objet proxy d'origine.
Un exemple bien connu de modèle proxy est le comptage de références : lorsque plusieurs copies d'un objet complexe sont nécessaires, le modèle proxy peut être combiné avec le modèle flyweight pour réduire l'utilisation de la mémoire. Une approche typique consiste à créer un objet complexe et plusieurs délégués, chacun faisant référence à l'objet d'origine. Les opérations effectuées sur l'agent sont transmises à l'objet d'origine. Une fois que tous les délégués n’existent plus, l’objet complexe est supprimé.
C'est très simple de comprendre le modèle de l'agence. En fait, le modèle de l'agence existe dans la vie :
Quand on achète des billets de train, on peut aller à la gare pour les acheter, mais on peut aussi. allez dans les agences de billets de train pour les acheter. Les billets de train ici L'agence de vente est l'agent qui achète les billets à la gare, c'est-à-dire que nous envoyons une demande d'achat de billets à l'agence de vente, et l'agence de vente enverra la demande. à la gare. La gare enverra une réponse d'achat réussie à l'agence commerciale, qui vous en informera alors.
Cependant, l'agence de vente ne peut acheter que des billets et ne peut pas les rembourser, tandis que la gare peut acheter et rembourser des billets, donc les opérations prises en charge par l'objet proxy peuvent être différentes des opérations de l'objet délégué.
Donnez un autre exemple que vous rencontrerez lors de l'écriture d'un programme :
Si vous avez un projet existant (vous n'avez pas le code source, vous pouvez seulement l'appeler), vous pouvez appeler int calculate( String exp1) pour implémenter le calcul de l'expression du suffixe, si vous souhaitez utiliser ce projet pour implémenter le calcul de l'expression infixe, vous pouvez alors écrire une classe proxy et également y définir un calcul (String exp2). Le paramètre exp2 est un infixe. expression, vous devez donc convertir l'expression infixe en expression postfixe (Prétraiter) avant d'appeler calculate() du projet existant, puis appeler computing() du projet existant. Bien sûr, vous pouvez également effectuer d'autres opérations telles que la sauvegarde après. recevant la valeur de retour Entrez le fichier (Postprocess), ce processus utilise le mode proxy.
Nous rencontrerons également des applications en mode proxy lorsque nous utilisons des ordinateurs :
Proxy à distance : Nous ne pouvons pas accéder à Facebook en Chine à cause de GFW. Nous pouvons y accéder en contournant le pare-feu (mise en place d'un proxy). Le processus d'accès est :
(1) L'utilisateur envoie la requête HTTP au proxy
(2) Le proxy envoie la requête HTTP au serveur web
(3) Le serveur web envoie la réponse HTTP à le proxy
(4 ) Le proxy renvoie la réponse HTTP à l'utilisateur
2. Proxy statique
Le proxy dit statique consiste à générer une classe proxy pendant la phase de compilation pour réaliser une série d’opérations sur l’objet proxy. Voici le diagramme de classes structurel du mode proxy :
1. Participants du mode proxy
Les rôles du mode proxy sont divisés en quatre types :
Thème interface : c'est-à-dire toutes les classes proxy. L'interface comportementale implémentée.
Objet cible : c'est-à-dire l'objet proxy.
Objet proxy : une classe proxy utilisée pour encapsuler la vraie classe de thème
Client
Voici la structure du diagramme de classes du mode proxy :
2. Proxy L'idée d'implémentation du modèle
L'objet proxy et l'objet cible implémentent la même interface comportementale.
La classe proxy et la classe cible implémentent respectivement la logique d'interface.
Instancier un objet cible dans le constructeur de la classe proxy.
Appelez l'interface comportementale de l'objet cible dans la classe proxy.
Si le client souhaite appeler l'interface comportementale de l'objet cible, il ne peut le faire que via la classe proxy.
3. Exemple de proxy statique
Ce qui suit utilise un exemple de chargement retardé pour illustrer le proxy statique. Lorsque nous démarrons un système de service, le chargement d'une certaine classe peut prendre beaucoup de temps. Afin d'obtenir de meilleures performances, lors du démarrage du système, nous n'initialisons souvent pas cette classe complexe, mais plutôt sa classe proxy. De cette manière, les méthodes qui consomment beaucoup de ressources sont séparées à l'aide de proxys, ce qui peut accélérer le démarrage du système et réduire le temps d'attente des utilisateurs.
Définir une interface de thème
public interface Subject { public void sayHello(); public void sayGoodBye(); }
Définir une classe cible et implémenter l'interface de thème
public class RealSubject implements Subject { public void sayHello() { System.out.println("Hello World"); } public void sayGoodBye() { System.out.println("GoodBye World"); } }
Définition Une classe proxy pour proxy l'objet cible.
public class StaticProxy implements Subject { Private RealSubject realSubject = null; public StaticProxy() {} public void sayHello() { //用到时候才加载, 懒加载 if(realSubject == null) { realSubject = new RealSubject(); } realSubject.sayHello(); } //sayGoodbye方法同理 ... }
Définir un client
public class Client { public static void main(String [] args) { StaticProxy sp = new StaticProxy(); sp.sayHello(); sp.sayGoodBye(); } }
Ce qui précède est un exemple de test simple d'un proxy statique. On a l’impression que cela n’a aucune utilité pratique. Ce n’est pas le cas. À l'aide de proxys, nous pouvons également transformer les méthodes de l'objet cible. Par exemple, une série de connexions sont créées dans le pool de connexions de la base de données. Afin de garantir que les connexions ne sont pas ouvertes fréquemment, ces connexions ne sont presque jamais fermées. Cependant, lorsque nous programmons, nous avons toujours l'habitude de fermer la connexion ouverte. De cette façon, nous pouvons utiliser le mode proxy pour re-proxy la méthode close dans l'interface Connection et la modifier pour la recycler dans le pool de connexions à la base de données au lieu d'exécuter réellement la méthode Connection#close. Il existe de nombreux autres exemples et vous devez les expérimenter vous-même.
3. Proxy dynamique
Le proxy dynamique fait référence à la génération dynamique de classes proxy au moment de l'exécution. Autrement dit, le bytecode de la classe d'agent sera généré au moment de l'exécution et chargé dans le ClassLoader de l'agent actuel. Les classes dynamiques présentent de nombreux avantages par rapport aux classes de traitement statiques.
不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也很麻烦。如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;
使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性。
生成动态代理的方法有很多: JDK中自带动态代理, CGlib, javassist等。这些方法各有优缺点。本文主要探究JDK中的动态代理的使用和源码分析。
下面用一个实例讲解一下JDK中动态代理的用法:
public class dynamicProxy implements InvocationHandler { private RealSubject = null; public Object invoke(Object proxy, Method method, Object[] args){ if(RealSubject == null) { RealSubject = new RealSubject(); } method.invoke(RealSubject, args); return RealSubject; } }
客户端代码实例
public class Client { public static void main(Strings[] args) { Subject subject = (Subject)Proxy.newInstance(ClassLoader.getSystemLoader(), RealSubject.class.getInterfaces(), new DynamicProxy()); Subject.sayHello(); Subject.sayGoodBye(); } }
从上面的代码可以看出, 要利用JDK中的动态代理。利用静态方法Proxy.newInstance(ClassLoader, Interfaces[], InvokeHandler)可以创建一个动态代理类。 newInstance方法有三个参数, 分别表示类加载器, 一个希望该代理类实现的接口列表, 以及实现InvokeHandler接口的实例。 动态代理将每个方法的执行过程则交给了Invoke方法处理。
JDK动态代理要求, 被代理的必须是个接口, 单纯的类则不行。JDK动态代理所生成的代理类都会继承Proxy类,同时代理类会实现所有你传入的接口列表。因此可以强制类型转换成接口类型。 下面是Proxy的结构图。
可以看出Proxy全是静态方法, 因此如果代理类没有实现任何接口, 那么他就是Proxy类型, 没有实例方法。
当然加入你要是非要代理一个没有实现某个接口的类, 同时该类的方法与其他接口定义的方法相同, 利用反射也是可以轻松实现的。
public class DynamicProxy implements InvokeHandler { //你想代理的类 private TargetClass targetClass = null; //初始化该类 public DynamicProxy(TargetClass targetClass) { this.targetClass = targetClass; } public Object invoke(Object proxy, Method method, Object[] args) { //利用反射获取你想代理的类的方法 Method myMethod = targetClass.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes()); myMethod.setAccessible(true); return myMethod.invoke(targetClass, args); } }
四、JDK动态代理源码分析(JDK7)
看了上面的例子, 我们只是简单会用动态代理。但是对于代理类是如何创建出来的, 是谁调用Invoke方法等还云里雾里。下面通过分析
1、代理对象是如何创建出来的?
首先看Proxy.newInstance方法的源码:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { } //获取接口信息 final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } //生成代理类 Class<?> cl = getProxyClass0(loader, intfs); // ...OK我们先看前半截 }
从源码看出代理类的生成是依靠getProxyClass0这个方法, 接下来看getProxyClass0源码:
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { //接口列表数目不能超过0xFFFF if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } //注意这里, 下面详细解释 return proxyClassCache.get(loader, interfaces); }
对proxyClassCache.get的解释是: 如果实现接口列表的代理类已经存在,那么直接从cache中拿。如果不存在, 则通过ProxyClassFactory生成一个。
在看proxyClassCache.get源码之前,先简单了解一下proxyClassCache:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
proxyClassCache是一个WeakCache类型的缓存, 它的构造函数有两个参数, 其中一个就是用于生成代理类的ProxyClassFactory, 下面是proxyClassCache.get的源码:
final class WeakCache<K, P, V> { ... public V get(K key, P parameter) {} }
这里K表示key, P表示parameters, V表示value
public V get(K key, P parameter) { //java7 NullObject判断方法, 如果parameter为空则抛出带有指定消息的异常。 如果不为空则返回。 Objects.requireNonNull(parameter); //清理持有弱引用的WeakHashMap这种数据结构,一般用于缓存 expungeStaleEntries(); //从队列中获取cacheKey Object cacheKey = CacheKey.valueOf(key, refQueue); //利用懒加载的方式填充Supplier, Concurrent是一种线程安全的map ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap != null) { valuesMap = oldValuesMap; } } // create subKey and retrieve the possible Supplier<V> stored by that // subKey from valuesMap Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null; while (true) { if (supplier != null) { // 从supplier中获取Value,这个Value可能是一个工厂或者Cache的实 //下面这三句代码是核心代码, 返回实现InvokeHandler的类并包含了所需要的信息。 V value = supplier.get(); if (value != null) { return value; } } // else no supplier in cache // or a supplier that returned null (could be a cleared CacheValue // or a Factory that wasn't successful in installing the CacheValue) //下面这个过程就是填充supplier的过程 if(factory == null) { //创建一个factory } if(supplier == null) { //填充supplier }else { //填充supplier } }
while循环的作用就是不停的获取实现InvokeHandler的类, 这个类可以是从缓存中拿到,也可是是从proxyFactoryClass生成的。
Factory是一个实现了Supplier
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { /* Verify that the class loader resolves the name of this interface to the same Class object.*/ Class<?> interfaceClass = null; try { //加载每一个接口运行时的信息 interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } //如果使用你自己的classload加载的class与你传入的class不相等,抛出异常 if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } //如果传入不是一个接口类型 if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } //验证接口是否重复 if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName()); } } 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. */ //这一段是看你传入的接口中有没有不是public的接口,如果有,这些接口必须全部在一个包里定义的,否则抛异常 for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { String name = intf.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, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); //生成随机代理类的类名, $Proxy + num String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * 生成代理类的class文件, 返回字节流 */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { //结束 throw new IllegalArgumentException(e.toString()); } } }
前文提到ProxyFactoryClass#apply是真正生成代理类的方法, 这其实是不准确的。源代码读到这里,我们会发现ProxyGenerator#generateProxyClass才是真正生成代理类的方法。根据Java class字节码组成(可以参见我的另一篇文章Java字节码学习笔记)来生成相应的Clss文件。具体ProxyGenerator#generateProxyClass源码如下:
private byte[] generateClassFile() { /* * Step 1: Assemble ProxyMethod objects for all methods to * generate proxy dispatching code for. */ //addProxyMethod方法,就是将方法都加入到一个列表中,并与对应的class对应起来 //这里给Object对应了三个方法hashCode,toString和equals addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); //将接口列表中的接口与接口下的方法对应起来 for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); } } /* * For each set of proxy methods with the same signature, * verify that the methods' return types are compatible. */ for (List<ProxyMethod> sigmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } /* * Step 2: Assemble FieldInfo and MethodInfo structs for all of * fields and methods in the class we are generating. */ //方法中加入构造方法,这个构造方法只有一个,就是一个带有InvocationHandler接口的构造方法 //这个才是真正给class文件,也就是代理类加入方法了,不过还没真正处理,只是先加进来等待循环,构造方法在class文件中的名称描述是<init> try { methods.add(generateConstructor()); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { //给每一个代理方法加一个Method类型的属性,数字10是class文件的标识符,代表这些属性都是private static的 fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); //将每一个代理方法都加到代理类的方法中 methods.add(pm.generateMethod()); } } //加入一个静态初始化块,将每一个属性都初始化,这里静态代码块也叫类构造方法,其实就是名称为<clinit>的方法,所以加到方法列表 methods.add(generateStaticInitializer()); } catch (IOException e) { throw new InternalError("unexpected I/O Exception"); } //方法和属性个数都不能超过65535,包括之前的接口个数也是这样, //这是因为在class文件中,这些个数都是用4位16进制表示的,所以最大值是2的16次方-1 if (methods.size() > 65535) { throw new IllegalArgumentException("method limit exceeded"); } if (fields.size() > 65535) { throw new IllegalArgumentException("field limit exceeded"); } //接下来就是写class文件的过程, 包括魔数, 类名,常量池等一系列字节码的组成,就不一一细说了。需要的可以参考JVM虚拟机字节码的相关知识。 cp.getClass(dotToSlash(className)); cp.getClass(superclassName); for (int i = 0; i < interfaces.length; i++) { cp.getClass(dotToSlash(interfaces[i].getName())); } cp.setReadOnly(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); try { // u4 magic; dout.writeInt(0xCAFEBABE); // u2 minor_version; dout.writeShort(CLASSFILE_MINOR_VERSION); // u2 major_version; dout.writeShort(CLASSFILE_MAJOR_VERSION); cp.write(dout); // (write constant pool) // u2 access_flags; dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); // u2 this_class; dout.writeShort(cp.getClass(dotToSlash(className))); // u2 super_class; dout.writeShort(cp.getClass(superclassName)); // u2 interfaces_count; dout.writeShort(interfaces.length); // u2 interfaces[interfaces_count]; for (int i = 0; i < interfaces.length; i++) { dout.writeShort(cp.getClass( dotToSlash(interfaces[i].getName()))); } // u2 fields_count; dout.writeShort(fields.size()); // field_info fields[fields_count]; for (FieldInfo f : fields) { f.write(dout); } // u2 methods_count; dout.writeShort(methods.size()); // method_info methods[methods_count]; for (MethodInfo m : methods) { m.write(dout); } // u2 attributes_count; dout.writeShort(0); // (no ClassFile attributes for proxy classes) } catch (IOException e) { throw new InternalError("unexpected I/O Exception"); } return bout.toByteArray(); }
经过层层调用, 一个代理类终于生成了。
2、是谁调用了Invoke?
我们模拟JDK自己生成一个代理类, 类名为TestProxyGen:
public class TestGeneratorProxy { public static void main(String[] args) throws IOException { byte[] classFile = ProxyGenerator.generateProxyClass("TestProxyGen", Subject.class.getInterfaces()); File file = new File("/Users/yadoao/Desktop/TestProxyGen.class"); FileOutputStream fos = new FileOutputStream(file); fos.write(classFile); fos.flush(); fos.close(); } }
用JD-GUI反编译该class文件, 结果如下:
import com.su.dynamicProxy.ISubject; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class TestProxyGen extends Proxy implements ISubject { private static Method m3; private static Method m1; private static Method m0; private static Method m4; private static Method m2; public TestProxyGen(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final void sayHello() throws { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final boolean equals(Object paramObject) throws { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() throws { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void sayGoodBye() throws { try { this.h.invoke(this, m4, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() throws { try { return (String)this.h.invoke(this, m2, null); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m3 = Class.forName("com.su.dynamicProxy.ISubject").getMethod("sayHello", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m4 = Class.forName("com.su.dynamicProxy.ISubject").getMethod("sayGoodBye", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
首先注意到生成代理类的构造函数, 它传入一个实现InvokeHandler接口的类作为参数, 并调用父类Proxy的构造器, 即将Proxy中的成员变量protected InvokeHander h进行了初始化。
再次注意到几个静态的初始化块, 这里的静态初始化块就是对代理的接口列表以及hashcode,toString, equals方法进行初始化。
最后就是这几个方法的调用过程, 全都是回调Invoke方法。
就此代理模式分析到此结束。
更多Explication détaillée du mode proxy dans les modèles de conception et son implémentation dans les programmes Java相关文章请关注PHP中文网!