1. プロキシ パターンの定義
オブジェクトにプロキシ オブジェクトを提供し、プロキシ オブジェクトは元のオブジェクトへのアクセスを制御します。つまり、クライアントは元のオブジェクトを直接制御せず、プロキシ オブジェクトを通じて間接的に元のオブジェクトを制御します。 。
プロキシ パターンのよく知られた例は参照カウントです。複雑なオブジェクトの複数のコピーが必要な場合、プロキシ パターンをフライウェイト パターンと組み合わせてメモリ使用量を削減できます。一般的なアプローチは、複雑なオブジェクトと複数のデリゲートを作成し、それぞれが元のオブジェクトを参照することです。エージェント上で実行された操作は、元のオブジェクトに転送されます。すべてのデリゲートが存在しなくなると、複合オブジェクトは削除されます。
代理店モデルを理解するのは非常に簡単です。実際、代理店モデルは生活の中に存在します。
電車の切符を買うとき、駅に行って買うこともできますが、電車の切符売り場に行くこともできます。ここでのチケット購入代理店とは、販売代理店にチケット購入リクエストを発行し、鉄道駅が購入成功の応答を送信することを意味します。販売代理店、販売代理店が教えてくれます。
ただし、切符は営業所でのみ購入でき、払い戻しはできませんが、切符は駅で購入および払い戻しができるため、代理オブジェクトによってサポートされる操作は、委任されたオブジェクトの操作とは異なる場合があります。
プログラムを作成するときに遭遇する別の例:
既存のプロジェクトがあり (ソース コードがない場合は、それを呼び出すことしかできません)、 int compute(String exp1) を呼び出してサフィックスを計算できます。このプロジェクトを使用して中置式の計算を実装したい場合は、プロキシ クラスを作成し、その中で compute(String exp2) を定義できます。この exp2 パラメータは中置式であるため、既存の を呼び出す必要があります。 project compute() の前に、中置式を後置式に変換し (Preprocess)、既存のプロジェクトの compute() を呼び出します。 もちろん、戻り値を受け取った後にファイルへの保存などの他の操作を行うこともできます。 (ポストプロセス) このプロセスはプロキシ モードを使用します。
コンピューターを使用するときに、プロキシ モード アプリケーションにも遭遇します。
リモート プロキシ: GFW のせいで、中国では Facebook にアクセスできません。壁をバイパスする (プロキシを設定する) ことでアクセスできます。アクセスプロセスは次のとおりです:
(1) ユーザーが HTTP リクエストをプロキシに送信します
(2) プロキシが HTTP リクエストを Web サーバーに送信します
(3) Web サーバーが HTTP レスポンスをプロキシに送信します
(4)プロキシは HTTP 応答をユーザー
に送り返します。 2. 静的プロキシ
いわゆる静的プロキシは、コンパイル段階でプロキシ クラスを生成し、プロキシ オブジェクトに対する一連の操作を完了します。以下は、プロキシ モードの構造クラス図です。
1. プロキシ モードの参加者
プロキシ モードの役割は 4 つのタイプに分けられます:
テーマ インターフェイス: つまり、プロキシによって実装される動作インターフェイスクラス。
ターゲット オブジェクト: つまり、プロキシされるオブジェクトです。
プロキシ オブジェクト: 実際のテーマ クラスをカプセル化するために使用されるプロキシ クラス
クライアント
以下はプロキシ モードのクラス図構造です:
2. プロキシ モードの実装アイデア
両方のプロキシオブジェクトとターゲット オブジェクトは同じ動作インターフェイスを実装します。
プロキシ クラスとターゲット クラスはそれぞれインターフェイス ロジックを実装します。
プロキシクラスのコンストラクターでターゲットオブジェクトをインスタンス化します。
プロキシクラスでターゲットオブジェクトの動作インターフェースを呼び出します。
クライアントがターゲット オブジェクトの動作インターフェイスを呼び出したい場合、プロキシ クラスを介してのみ呼び出すことができます。
3. 静的プロキシの例
以下では、遅延読み込みの例を使用して静的プロキシを説明します。あるサービスシステムを起動すると、あるクラスのロードに時間がかかることがあります。より良いパフォーマンスを得るために、システムの起動時に、この複雑なクラスを初期化せず、代わりにそのプロキシ クラスを初期化することがよくあります。このように、リソースを多く消費するメソッドがプロキシを使用して分離されるため、システムの起動が高速化され、ユーザーの待ち時間が短縮されます。
テーマインターフェイスを定義します
public interface Subject { public void sayHello(); public void sayGoodBye(); }
ターゲットクラスを定義し、テーマインターフェイスを実装します
public class RealSubject implements Subject { public void sayHello() { System.out.println("Hello World"); } public void sayGoodBye() { System.out.println("GoodBye World"); } }
ターゲットオブジェクトをプロキシするプロキシクラスを定義します。
public class StaticProxy implements Subject { Private RealSubject realSubject = null; public StaticProxy() {} public void sayHello() { //用到时候才加载, 懒加载 if(realSubject == null) { realSubject = new RealSubject(); } realSubject.sayHello(); } //sayGoodbye方法同理 ... }
クライアントを定義する
public class Client { public static void main(String [] args) { StaticProxy sp = new StaticProxy(); sp.sayHello(); sp.sayGoodBye(); } }
上記は、静的プロキシの簡単なテスト例です。実用性は無さそうな気がします。そうではありません。プロキシを使用すると、ターゲット オブジェクトのメソッドを変換することもできます。たとえば、接続が頻繁に開かれないようにするために、これらの接続はほとんど閉じられません。ただし、プログラミングするときは、常に開いている接続を閉じる習慣があります。 このように、プロキシ モードを使用して、Connection インターフェイスの close メソッドを再プロキシし、Connection#close メソッドを実際に実行する代わりにデータベース接続プールにリサイクルされるように変更できます。他にもたくさんの例がありますので、実際に体験してみてください。
3. 動的プロキシ
動的プロキシとは、実行時に動的に生成されるプロキシ クラスを指します。つまり、エージェント クラスのバイトコードが実行時に生成され、現在のエージェントの ClassLoader にロードされます。動的クラスには、静的処理クラスと比較して多くの利点があります。
不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也很麻烦。如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;
使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性。
生成动态代理的方法有很多: 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方法。
就此代理模式分析到此结束。
更多デザインパターンにおけるプロキシモードとJavaプログラムでの実装の詳細な説明相关文章请关注PHP中文网!