日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java 技术之动态代理机制

發布時間:2025/3/20 java 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 技术之动态代理机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章推薦

  • 精選java等全套學習資源
  • 精選java電子圖書資源
  • 精選大數據學習資源
  • java項目練習精選
###靜態代理

常規的代理模式有以下三個部分組成:
功能接口

interface IFunction {void doAThing(); }

功能提供者

class FunctionProvider implement IFunction {public void doAThing {System.out.print("do A");} }

功能代理者

class Proxy implement IFunction {private FunctionProvider provider;Proxy(FunctionProvider provider) {this.provider = provider;}public void doAThing {provider.doAThing();} }

前兩者就是普通的接口和實現類,而第三個就是所謂的代理類。對于使用者而言,他會讓代理類去完成某件任務,并不關心這件任務具體的跑腿者。

這就是靜態代理,好處是方便調整變換具體實現類,而使用者不會受到任何影響。

不過這種方式也存在弊端:比如有多個接口需要進行代理,那么就要為每一個功能提供者創建對應的一個代理類,那就會越來越龐大。而且,所謂的“靜態”代理,意味著必須提前知道被代理的委托類。

通過下面一個例子來說明下:
統計函數耗時–靜態代理實現

現在希望通過一個代理類,對我感興趣的方法進行耗時統計,利用靜態代理有如下實現:

interface IAFunc {void doA(); } interface IBFunc {void doB(); }class TimeConsumeProxy implement IAFunc, IBFunc {private AFunc a;private BFunc b;public(AFunc a, BFunc b) {this.a = a;this.b = b;}void doA() {long start = System.currentMillions();a.doA();System.out.println("耗時:" + (System.currentMillions() - start));}void doB() {long start = System.currentMillions();b.doB();System.out.println("耗時:" + (System.currentMillions() - start));} }

弊端很明顯,如果接口越多,每新增一個函數都要去修改這個 TimeConsumeProxy 代理類:把委托類對象傳進去,實現接口,在函數執行前后統計耗時。

這種方式顯然不是可持續性的,下面來看下使用動態代理的實現方式,進行對比。
###動態代理

動態代理的核心思想是通過 Java Proxy 類,為傳入進來的任意對象動態生成一個代理對象,這個代理對象默認實現了原始對象的所有接口。

還是通過統計函數耗時例子來說明更加直接。
統計函數耗時–動態代理實現

interface IAFunc {void doA(); } interface IBFunc {void doB(); }class A implement IAFunc { ... } class B implement IBFunc { ... }class TimeConsumeProxy implements InvocationHandler {private Object realObject;public Object bind(Object realObject) {this.realObject = realObject;Object proxyObject = Proxy.newInstance(realObject.getClass().getClassLoader(),realObject.getClass().getInterfaces(),this);return proxyObject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long start = System.currentMillions();Object result = method.invoke(target, args);System.out.println("耗時:" + (System.currentMillions() - start));return result;} }

具體使用時:

public static void main(String[] args) {A a = new A();IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);aProxy.doA();B b = new B();IBFunc bProxy = (IBFunc) new TimeConsumeProxy().bind(b);bProxy.doB(); }

這里最大的區別就是:代理類和委托類 互相透明獨立,邏輯沒有任何耦合,在運行時才綁定在一起。這也就是靜態代理與動態代理最大的不同,帶來的好處就是:無論委托類有多少個,代理類不受到任何影響,而且在編譯時無需知道具體委托類。

回到動態代理本身,上面代碼中最重要的就是:

Object proxyObject = Proxy.newInstance(realObject.getClass().getClassLoader(),realObject.getClass().getInterfaces(),this);

通過 Proxy 工具,把真實委托類轉換成了一個代理類,最開始提到了一個代理模式的三要素:功能接口、功能提供者、功能代理者;在這里對應的就是:realObject.getClass().getInterfaces(),realObject,TimeConsumeProxy。

其實動態代理并不復雜,通過一個 Proxy 工具,為委托類的接口自動生成一個代理對象,后續的函數調用都通過這個代理對象進行發起,最終會執行到 InvocationHandler#invoke 方法,在這個方法里除了調用真實委托類對應的方法,還可以做一些其他自定義的邏輯,比如上面的運行耗時統計等。
###探索動態代理實現機制

好了,上面我們已經把動態代理的基本用法及為什么要用動態代理進行了講解,很多文章到這里也差不多了,不過我們還準備進一步探索一下給感興趣的讀者。

拋出幾個問題:

上面生成的代理對象 Object proxyObject 究竟是個什么東西?為什么它可以轉型成 IAFunc,還能調用doA() 方法?
這個 proxyObject 是怎么生成出來的?它是一個 class 嗎?

下面我先給出答案,再一步步探究這個答案是如何來的。
問題一: proxyObject 究竟是個什么 -> 動態生成的 $Proxy0.class 文件

在調用 Proxy.newInstance 后,Java 最終會為委托類 A 生成一個真實的 class 文件:$Proxy0.class,而 proxyObject 就是這個 class 的一個實例。

猜一下,這個 $Proxy0.class 類長什么樣呢,包含了什么方法呢?回看下剛剛的代碼:

IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a); aProxy.doA();

推理下,顯然這個 $Proxy0.class 實現了 IAFunc 接口,同時它內部也實現了 doA() 方法,而且重點是:這個 doA() 方法在運行時會執行到 TimeConsumeProxy#invoke() 方法里。

重點來了!下面我們來看下這個 $Proxy0.class 文件,把它放進 IDE 反編譯下,可以看到如下內容,來驗證下剛剛的猜想:

final class $Proxy0 extends Proxy implements IAFunc {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler var1) throws {super(var1);}public final boolean equals(Object var1) throws {// 省略}public final void doA() throws {try {// 劃重點super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final String toString() throws {// 省略}public final int hashCode() throws {// 省略}static {try {// 劃重點m3 = Class.forName("proxy.IAFunc").getMethod("doA", new Class[0]);m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}} }

沒錯,剛剛的猜想都中了!實現了 IAFunc 接口和 doA() 方法,不過,doA()里是什么鬼?

super.h.invoke(this, m3, (Object[])null);

回看下,TimeConsumeProxy里面的 invoke 方法,它的函數簽名是啥?

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

沒錯,doA()里做的就是調用 TimeConsumeProxy#invoke() 方法。

那么也就是說,下面這段代碼執行流程如下:

IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a); aProxy.doA();

基于傳入的委托類 A,生成一個$Proxy0.class 文件;
創建一個 $Proxy0.class 對象,轉型為 IAFunc 接口;
調用 aProxy.doA() 時,自動調用 TimeConsumeProxy 內部的 invoke 方法。

問題二:proxyObject 是怎么一步步生成出來的 -> $Proxy0.class 文件生成流程

剛剛從末尾看了結果,現在我們回到代碼的起始端來看:

Object proxyObject = Proxy.newInstance(realObject.getClass().getClassLoader(),realObject.getClass().getInterfaces(),this);

準備好,開始發車讀源碼了。我會截取重要的代碼并加上注釋。

先看Proxy.newInstance():

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {// 復制要代理的接口 final Class<?>[] intfs = interfaces.clone();// 重點:生成 $Proxy0.class 文件并通過 ClassLoader 加載進來Class<?> cl = getProxyClass0(loader, intfs);// 對 $Proxy0.class 生成一個實例,就是 `proxyObject`final Constructor<?> cons = cl.getConstructor(constructorParams);return cons.newInstance(new Object[]{h}); }

再來看 getProxyClass0 的具體實現:ProxyClassFactory工廠類:

@Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {// 參數為 ClassLoader 和要代理的接口Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);// 1. 驗證 ClassLoader 和接口有效性for (Class<?> intf : interfaces) {// 驗證 classLoader 正確性Class<?> interfaceClass = Class.forName(intf.getName(), false, loader);if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}// 驗證傳入的接口 class 有效if (!interfaceClass.isInterface()) { ... } // 驗證接口是否重復if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { ... }}// 2. 創建包名及類名 $Proxy0.classproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;// 3. 創建 class 字節碼內容byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);// 4. 基于字節碼和類名,生成 Class<?> 對象return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); }

再看下第三步生成 class 內容 ProxyGenerator.generateProxyClass:

// 添加 hashCode equals toString 方法 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]);} } // 添加構造函數 methods.add(this.generateConstructor());

這里構造好了類的內容:添加必要的函數,實現接口,構造函數等,下面就是要寫入上一步看到的 $Proxy0.class 了。

ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); dout.writeInt(0xCAFEBABE); ... dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); ... return bout.toByteArray();

到這里就生成了第一步看到的 $Proxy0.class 文件了,完成閉環,講解完成!
###動態代理小結

通過上面的講解可以看出,動態代理可以隨時為任意的委托類進行代理,并可以在 InvocationHandler#invoke 拿到運行時的信息,并可以做一些切面處理。

在動態代理背后,其實是為一個委托類動態生成了一個 $Proxy0.class 的代理類,該代理類會實現委托類的接口,并把接口調用轉發到 InvocationHandler#invoke 上,最終調用到真實委托類的對應方法。

動態代理機制把委托類和代理類進行了隔離,提高了擴展性。
###Java 動態代理與 Python 裝飾器

這是 Java 語言提供的一個有意思的語言特性,而其實 Python 里也提供了一種類似的特性:裝飾器,可以達到類似的面相切面編程思想,下次有空再把兩者做下對比,這次先到這。

轉自:https://juejin.im/entry/5a82fd0f6fb9a0633757368c
文章有不當之處,歡迎指正,你也可以關注我的微信公眾號:好好學java,獲取優質資源。

總結

以上是生活随笔為你收集整理的Java 技术之动态代理机制的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。