Java代理3:二刷代理

  • 2020 年 1 月 22 日
  • 筆記

靜態代理

  • Subject:抽象角色。可以是介面,也可以是抽象類。聲明真實對象(Real Subject)和代理對象(Proxy Subject)的共同介面。
  • Real Subject:真實角色。定義代理角色所代表的真實實體,即最終要引用的對象。
  • Proxy Subjcet:代理角色。包含真實對象的引用,負責對真實對象的調用,並在真實對象處理前後做預處理與善後工作;提供與真是對象(Real Subject)相同的介面以便任何時候都可以代替真實實體。

為保持行為的一致性,代理角色與真實角色通常會實現相同的介面。

詳細程式碼實例請見:Java代理1 代理和動態代理的基礎與使用 中的靜態代理部分。

優點

  • 職責清晰:真是角色只需關注業務邏輯的實現。非業務邏輯的部分交給後期的代理角色即可。
  • 高擴展:可以在不修改真實對象的前提下,對目標功能擴展。

缺點

  • 代理角色的一個介面對應一種類型的對象(即代理角色與真實角色通常會實現相同的介面),如果要代理的過多,就會產生大量的類,從而導致類的急速膨脹。
  • 介面添加一個方法後,所有實現類都要實現該方法,所有的代理類也需要實現該方法。增加了程式碼維護的複雜度。

JDK Proxy動態代理

在使用動態代理時,需要定義一個位於代理類與委託類之間的中介類,也叫動態代理類,該類被要求實現InvocationHandler介面。

當調用代理類對象的方法時,這個「調用」會轉送到中介類的invoke方法中。參數proxy為調用method的代理實例(即動態生成的那個代理類),method標識了我們具體調用的是代理類的哪個方法,args為這個方法的參數。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

一個典型的動態代理可分為以下四個步驟:

  • 創建抽象角色(如:Animal)
  • 創建真實角色(如:Dog)
  • 通過實現InvocationHandler介面創建中介類(如:DynamicProxyHandler)
  • 通過場景類,動態生成代理類(如:SimpleDynamicProxy)

詳細程式碼實例請見:Java代理2 動態代理的實現原理分析

在場景類中通過如下程式碼動態產生了一個代理類,並返回了其實例:

    Animal prox = (Animal) Proxy.newProxyInstance(              Animal.class.getClassLoader(),              new Class[]{Animal.class},              new DynamicProxyHandler(dog)      );

Proxy的newProxyInstance主要業務邏輯為:

// 1 獲得與傳入的指定類裝載器(loader)和介面列表(intfs)相關的代理類類型對象,亦既生成代理類class,並載入到JVM中  Class<?> cl = getProxyClass0(loader, intfs);  // 2 通過反射獲取該代理類的構造函數,亦既獲取代理類的參數類型為InvocationHandler的構造函數  final Constructor<?> cons = cl.getConstructor(constructorParams);  // 3 返回這個新的代理類的一個實例,亦既生成並返回代理類實例  return cons.newInstance(new Object[]{h});

具體做的事有:

  • 根據傳入的參數interfaces動態生成一個類,它實現interfaces中的介面,該例中即Animal介面的doBark方法和somethingElse方法。假設動態生成的類為$Proxy0。
  • 通過傳入的classloder(該例中即Animal.class.getClassLoader()),將剛生成的$Proxy0類載入到jvm中。
  • 利用中介類,調用$Proxy0的$Proxy0(InvocationHandler)構造函數,創建$Proxy0類的實例,其InvocationHandler屬性,為我們創建的中介類。

其核心為getProxyClass0方法。

    /**       * Generate a proxy class.  Must call the checkProxyAccess method       * to perform permission checks before calling this.       */      private static Class<?> getProxyClass0(ClassLoader loader,                                             Class<?>... interfaces) {          // 校驗介面數量          if (interfaces.length > 65535) {              throw new IllegalArgumentException("interface limit exceeded");          }          // 從快取中獲取,如果沒有就通過ProxyClassFactory創建          // If the proxy class defined by the given loader implementing          // the given interfaces exists, this will simply return the cached copy;          // otherwise, it will create the proxy class via the ProxyClassFactory          return proxyClassCache.get(loader, interfaces);      }

Proxy類中有一屬性proxyClassCache,這是一個java.lang.reflect.WeakCache類型的靜態變數,其指示了類載入器和代理類之間的映射。proxyClassCache的get方法用於根據類載入器來獲取Proxy類,如果已經存在則直接從cache中返回,如果沒有則創建一個映射並更新cache表。

proxyClassCache的get方法supplier.get() 當是創建代理類時,supplier為是Factory,這個類定義在WeakCach的內部。Factory中get方法又通過 value = Objects.requireNonNull(valueFactory.apply(key, parameter)); 調用了 java.lang.reflect.Proxy.ProxyClassFactory 的apply方法(valueFactory即ProxyClassFactory)。

最終通過apply中的如下語句生成代理類。

 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(                  proxyName, interfaces, accessFlags);

ProxyGenerator.generateProxyClass方法可以不填寫最後一個參數,從而我們可以通過如下程式碼手動生成代理類。

 byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",                  Dog.class.getInterfaces());

JDK的動態生成的代理類均會繼承Proxy類,由於java的單繼承特性,故之後只能跟隨實現介面,從而只能代理實現了介面的類。

CGLIB代理

JDK動態代理只能代理實現了介面的類。

CGLIB動態代理針對類實現代理,原理是對目標類生成一個子類,並覆蓋其中方法實現增強,由於是繼承,無法對final修飾的類進行代理。

實現步驟

  • 創建目標對象(如:DogCGlib)
  • 通過實現MethodInterceptor介面創建中介類(如:CGlibProxy)
  • 通過場景類,動態生成代理類(如:CGLIBDemo)

詳細程式碼實例請見:Java代理1 代理和動態代理的基礎與使用 中的CGLIB動態代理部分。

在場景類中通過如下程式碼動態產生了一個代理類,並返回了其實例:

 // proxy為CGlibProxy實例,即CGlibProxy proxy = new CGlibProxy();   DogCGlib dog = (DogCGlib)proxy.getInstance(DogCGlib.class);

getInstance要做的事有:

  • 創建Enhancer實例
  • 通過setSuperclass設置目標類
  • 通過setCallback設置攔截對象
  • create方法生成Target的代理類,並返回代理類的實例

Enhancer是CGLib的位元組碼增強器,可以方便的對類進行擴展,內部調用GeneratorStrategy.generate方法生成代理類的位元組碼,通過以下方式可以生成class文件:

 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./tmp/");

具體生成如下:

其中DogCGlib$$EnhancerByCGLIB$$556eb8b5.class為生成的代理類,進去可見

public class DogCGlib$$EnhancerByCGLIB$$556eb8b5 extends DogCGlib implements Factory {      ...        static void CGLIB$STATICHOOK1() {          CGLIB$THREAD_CALLBACKS = new ThreadLocal();          CGLIB$emptyArgs = new Object[0];          Class var0 = Class.forName("Others.base.SimpleProxy.CGLibDemo.DogCGlib$$EnhancerByCGLIB$$556eb8b5");          Class var1;          Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());          CGLIB$equals$2$Method = var10000[0];          CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");          CGLIB$toString$3$Method = var10000[1];          CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");          CGLIB$hashCode$4$Method = var10000[2];          CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");          CGLIB$clone$5$Method = var10000[3];          CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");          var10000 = ReflectUtils.findMethods(new String[]{"somethingElse", "(Ljava/lang/String;)V", "doBark", "()V"}, (var1 = Class.forName("Others.base.SimpleProxy.CGLibDemo.DogCGlib")).getDeclaredMethods());          CGLIB$somethingElse$0$Method = var10000[0];          CGLIB$somethingElse$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "somethingElse", "CGLIB$somethingElse$0");          CGLIB$doBark$1$Method = var10000[1];          CGLIB$doBark$1$Proxy = MethodProxy.create(var1, var0, "()V", "doBark", "CGLIB$doBark$1");      }        private MethodInterceptor CGLIB$CALLBACK_0;      ...      final void CGLIB$doBark$1() {          super.doBark();      }      public final void doBark() {          MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;          if (this.CGLIB$CALLBACK_0 == null) {              CGLIB$BIND_CALLBACKS(this);              var10000 = this.CGLIB$CALLBACK_0;          }            if (var10000 != null) {              var10000.intercept(this, CGLIB$doBark$1$Method, CGLIB$emptyArgs, CGLIB$doBark$1$Proxy);          } else {              super.doBark();          }      }      ...  }

每個被代理的方法都對應一個MethodProxy對象,methodProxy.invokeSuper方法最終調用委託類的doBark方法。

JDK代理通過反射,CGLIB通過MethodProxy+FastClass方式(類似索引的方式直接調用委託類方法)實現方法調用。

FastClass其實就是對Class對象進行特殊處理,提出下標概念index,通過索引保存方法的引用資訊,將原先的反射調用,轉化為方法的直接調用,從而體現所謂的fast。

CGLIB部分暫時跟不下去了,網上搜到的內容大量的同質化,擴展裡面的前三篇相對來說同質化的內容少一些。由於有些內容還不明白,導致不能像JDK動態代理那樣流暢。

問題如:

  • 怎樣通過生成的動態代理類調到intercept。
  • 動態代理類如何與FastClass相結合的。

核心其實就是:

 dog.doBark();

如何調到MethodInterceptor.intercept方法的,這個留給日後單獨開一篇探索吧。

參考資料

說說 JAVA 代理模式

Java的三種代理模式

java經典講解-靜態代理和動態代理的區別

Java三種代理模式:靜態代理、動態代理和cglib代理

Cglib動態代理實現原理

Tags: