歡迎光臨
每天分享高質量文章

Java 動態代理及 RPC 框架介紹

(點擊上方公眾號,可快速關註)


來源:Frapples ,

frapples.github.io/articles/2018-03-30-4a97.html

所謂動態代理,指的是語言提供的一種語法,能夠將對物件中不同方法的呼叫重定向到一個統一的處理函式中來。

python重寫__getattr__函式能夠做到這一點,就連世界上最好的語言也提供稱為魔術方法的__call。

這種語法除了能更好的實現動態代理外,還是RPC框架實現原理的一部分。

1. 動態代理是什麼

動態代理提供一種抽象,能夠將物件中不同方法的呼叫重定向到一個統一的處理函式,做自定義的邏輯處理。但是對於呼叫者,對此毫無察覺,就好像呼叫的方法是用傳統方式實現的一般。

這種語法,在java中被稱為動態代理。之所以叫做動態代理,是因為它能避免傳統代理樣式實現中人工一個一個的將java函式轉發過去,而是能夠讓代碼自動做到這一點,這樣代理類的代碼是和業務無關的,不會因為業務類的方法增多而逐漸龐大。使代碼更易維護更易修改,實現自動化搬磚。

實際上,被代理的類不一定位於本機類,動態代理語法提供了一種抽象方式,被代理的類也可以位於遠程主機上,這也是RPC框架實現原理的一部分。

理解了動態代理的概念後不難發現,動態代理概念上有著這麼幾個部分:

  1. 給呼叫者使用的代理類。在java中,我們發現動態代理提供的抽象天然契合面向接口編程,因此它也有可能是接口。

  2. 一個統一的處理函式,收集不同函式轉發過來的請求,可自定義處理邏輯集中處理。java中它可能會成為一個較獨立的部分,因此也可能是類。

2. java動態代理機制

理解了概念,就不難理解java動態代理的機制了。下麵來看看java動態代理機制如何代理一個本地物件。

2.1. 代理接口

首先看第一個部分,給呼叫者使用的代理類。在java動態代理機制中,這個角色只能是接口。我們定義一個整數運算接口:

interface NumberOperationInterface {

    int add(int a, int b);

}

2.2. 代理處理器

再看第二個角色,統一的處理函式。在java中它的確是類,通過實現InvocationHandler接口定義。

class NumberOperationImpProxyHandler implements InvocationHandler {

    private Object proxied;

    public RealObjectProxyHandler(Object proxied) {

        this.proxied = proxied;

    }

    @Override

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

        System.out.printf(“呼叫函式%s\n”, method.getName());

        return method.invoke(proxied, args);

    }

}

  1. 由於我們的例子是代理本地物件,那麼處理函式是需要被代理物件的信息。可以看到,我們從建構式中將被代理物件儲存在該類中,即可從處理函式中訪問到。

  2. 在invoke函式中,對代理物件的所有方法的呼叫都被轉發至該函式處理。在這裡可以靈活的自定義各種你能想到的邏輯。在上面的代碼中,我們使用反射呼叫被代理物件的同名方法實現。

2.3. 被代理類

由於我們的示例是代理本地物件,因此還需要一個被代理物件的類:

class NumberOperationImp implements NumerOperationInterface {

    @Override

    public int add(int a, int b) {

        return a + b;

    }

}

2.4. 創建代理物件

好了,各個組成部分都定義完成。現在把它們組合起來:

public NumerOperationInterface wrap(NumerOperationInterface proxied) {

    return (NumerOperationInterface) Proxy.newProxyInstance(

        NumerOperationInterface.class.getClassLoader(),

        new Class[]{NumerOperationInterface.class},

        new NumberOperationImpProxyHandler(proxied));

}

  1. 由於java提供的這個寫法實在是太啰嗦了,所以把它放入一個輔助函式中。

  2. Proxy.newProxyInstance 方法能夠根據提供的接口和代理處理器創建代理物件。

java提供的寫法太啰嗦了,可以考慮使用Guake提供的輔助函式簡化下代碼。如下:

public NumerOperationInterface wrap(NumerOperationInterface proxied) {

    return Reflection.newProxy(NumerOperationInterface.class, new NumberOperationImpProxyHandler(proxied));

}

好了,現在呼叫下試試:

NumerOperationInterface proxied = new NumberOperationImp();

real = wrap(proxied);

real.add(1, 2);

2.5. 總結

動態代理聽起來是代理樣式的動態實現,可是結合上面的最終效果,不覺得這個叫做動態裝飾器更合適嗎?

3. 動態代理的應用

說完了動態代理的概念和實現機制,該看看使用動態代理有哪些應用。

3.1. 應用一:代理樣式/裝飾器樣式的動態實現

這個應用場景前面據已經提到過。代理樣式和裝飾器樣式是編程當中很常用的技巧,用於提升代碼的靈活性和可擴展性。傳統代理樣式的實現方式比較暴力直接,需要將所有被代理類的所有方法都寫一遍,並且一個個的手動轉發過去。在維護被代理類的同時,作為java碼工還需要同時維護代理類的相關代碼,實在是累心。

通過使用動態代理,動態代理能夠自動將代理類的相關方法轉發到被代理類,可以看到:

  1. 代理轉發的過程自動化了,實現自動化搬磚。

  2. 代理類的代碼邏輯和具體業務邏輯解耦,與業務無關。

3.2. 應用二:實現AOP

是的,利用動態代理也能實現AOP。仔細推演一下不能得出這個結論。我們知道:

  1. 動態代理提供了一種方式,能夠將分散的方法呼叫轉發到一個統一的處理函式處理。

  2. AOP的實現需要能夠提供這樣一種機制,即在執行函式前和執行函式後都能執行自己定義的鉤子。

那麼,首先使用動態代理讓代理類忠實的代理被代理類,然後處理函式中插入我們的自定義的鉤子。之後讓代理類替換被代理類需要使用的場景,這樣,相當於對該類的所有方法定義了一個切麵。

不過,使用動態代理實現AOP特別麻煩,啰嗦。這僅僅作為一個探討的思路,來說明動態代理這一通用概念可以實現很多特定技術。實際使用中當然使用spring提供的AOP更為方便。

3.3. 應用三:實現RPC

RPC即遠程過程呼叫,在分佈式的網站架構中是一個非常重要的技術,目前現在流行的SOA架構,微服務架構,它們的核心原理之一就是RPC呼叫。

從概念上來說,RPC的概念是非常簡潔優美的。RPC方法的呼叫和普通的方法並無二異,呼叫者不需要操心具體的實現,這是抽象提供的威力。實現上,它將函式呼叫方和函式的提供方分散在兩個不同的行程上,中間使用網絡通信來進行資料交互。

動態代理就是實現RPC的技術之一。只要理解了動態代理和RPC,我們很容易發現這樣一個事實:RPC呼叫其實是對遠程另外一臺機器行程上的物件的代理。

仔細思考RPC呼叫的資料流流向,就能梳理出這樣的思路:

  1. 呼叫方呼叫本地的RPC代理方法,將引數提供給該方法。

  2. 不同的RPC代理方法被轉發到一個統一的處理中心,該處理中心知道呼叫的是那個函式,引數是什麼。

  3. 該處理中心將呼叫的信息封裝打包,通過網絡發送給另外一個行程。

  4. 另外一個行程接受到呼叫行程發送過來的資料包。

  5. 該行程根據資料包中記錄的RPC呼叫信息,將呼叫分發給對應的被代理物件的對應方法去執行。

  6. 傳回的話思路類似。

顯而易見,第二步,需要使用動態代理將分散的函式呼叫轉發到一個統一的處理中心;第五步,將統一收集來的呼叫信息分發給具體的函式執行,顯然使用反射做到這一點。

有了這個思路,通過利用動態代理,反射,和網絡編程技術,實現一個簡易版的RPC框架也就不難了。考慮到本文是介紹動態代理的,關於RPC的細節實現有時間新開一篇博文分析。

4. 最後

總得來說,通過一定的思考,個人覺得動態代理的核心在於:將分散的對物件不同方法的呼叫轉發到一個同一的處理函式中來。

有了這個關鍵點,很多其它技術的實現需要借助於動態代理的這一個關鍵點實現,也因此動態代理也有著這麼多的應用。

【關於投稿】


如果大家有原創好文投稿,請直接給公號發送留言。


① 留言格式:
【投稿】+《 文章標題》+ 文章鏈接

② 示例:
【投稿】《不要自稱是程式員,我十多年的 IT 職場總結》:http://blog.jobbole.com/94148/

③ 最後請附上您的個人簡介哈~



看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

赞(0)

分享創造快樂