1. 程式人生 > 其它 >【進階之路】動態代理與位元組碼生成

【進階之路】動態代理與位元組碼生成

https://www.cnblogs.com/nanjuryc/p/15083832.html

這段時間換了新工作,因為去了外企,所以對英語的要求突然猛增,現在每天靠著谷歌翻譯過日子。在開會的時候,經常遇到不懂的單詞,很多時候都需要記下讀音,事後再慢慢根據語境去找對應的單詞,日子過得可謂是有滋有味。於是乎,自我充電的時間大部分用來學習英語了,所以這段時間更新的節奏會很慢~

對於大多數Java程式設計師而言,我們會經常用到位元組碼生成與動態代理技術,比如編譯時織入的AOP框架中,在Spring的Bean組織管理中,亦或是Web伺服器的JSP編譯器裡。總之,我們在不知不覺中已經大量的用到了這些技術了。

動態代理中所說的動態,是基於Java程式碼實際編寫代理類的靜態代理而言的。相比較而言,它的優勢在於可以在不知道原始類與介面的時候,就先確定了代理行為,可以很方便的在不同的應用場景中靈活運用。同時,還可以減少程式碼的行數,讓你的程式碼更加美觀和簡潔。

一、動態代理

這邊簡單的實現一個動態代理的方法,如果想看基於AOP與註解形式的,可以去看我之前的文章,也講的很詳細【進階之路】自定義註解介紹與實戰

public class DynamicTest {
    public static void main(String[] args) {
        IPayment pay = (IPayment) new BankDynamicProxy().bind(new Alipay());
        pay.payment();
    }

    interface IPayment {
        void payment();
    }

    static class Alipay implements IPayment {
        @Override
        public void payment() {
            System.out.println("Use Alipay to payment");
        }
    }

    static class BankDynamicProxy implements InvocationHandler {
        Object dynamicProxy;

        Object bind(Object dynamicProxy) {
            this.dynamicProxy = dynamicProxy;
            return Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader()
                    , dynamicProxy.getClass().getInterfaces(), this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("access Bank Api");
            return method.invoke(dynamicProxy, args);
        }
    }
}

這個動態代理方法邏輯很簡單,就是在使用支付寶支付的時候去請求了一次銀行的介面。通過這個方法,我們可以使用debug的方法看到程式的驗證、優化、快取、位元組碼生成、類載入等一些列操作

但是我們這一次不用去探究全部的流程,只需要去了解位元組碼生成的操作。

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

這個方法會在執行時生產一個描述代理類的位元組碼byte[]陣列。

在debug中看實在是太麻煩,我們可以生成一個位元組碼檔案,通過反編譯來檢視具體的內容。

二、位元組碼生成

只需要在程式碼的main方法中加入下圖這個方法,就可以在指定的位置生成一個名為Proxy0.classProxy0.class的代理類檔案。當然,換成nanju.class也沒問題。

public static void main(String[] args) {

    byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", DynamicTest.class.getInterfaces());
    String path = "D:\\temp\\$Proxy0.class";
    try (FileOutputStream fos = new FileOutputStream(path)) {
        fos.write(classFile);
        fos.flush();
        System.out.println("Agent class file written successfully");
    } catch (Exception e) {
        System.out.println("file written fail");
    }
}

然後我們通過最簡單的方法,直接把class檔案,拖拽到IntelliJ IDEA工具中,IntelliJ自動反編譯為Java檔案。

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 {
    private static Method m1;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

從這個動態代理類反編譯的程式碼中可以看出,它為從Object中繼承來的equals(),toString()和hashCode()等方法都生成了對應的實現,並且統一呼叫了java.lang.reflect.InvocationHandler物件中的invoke()方法,只是傳入的引數和Method方法有所不同。所以無論動態代理什麼方法,其實執行的依舊是InvocationHandler中的額邏輯。

generateProxyClass()方法通過Class檔案的規範去拼接位元組碼,但是對於程式程式碼來說,這樣的拼接很消耗資源且只能產生高度模板化的程式碼。比起這樣的生成,現成的位元組碼庫更適合於生產上的實踐。

有需要的同學可以加我的公眾號,以後的最新的文章第一時間都在裡面,也可以找我要思維導圖