Java築基——代理模式及實戰

1. 引入

我們以買房子來舉例,小明和對象小花相處已經 3 年了,卻仍然沒有步入婚姻的殿堂。親愛的小花說家裏要求小明給她買套房子才肯結婚,因爲覺得這樣子小花以後的生活比較有保障,至少有個住的地方,同時也想考察一下小明的經濟實力行不行。

小明很無奈,但是必須得先買房。小明來到了省城買房,剛下車就有賣房中介擠過來,一個勁兒地介紹:想要多大平方的房子?我能幫你找到最合適的房子,我還會幫你搞定銀行貸款,不用你來回跑。小明想啊,“靠人不如靠己”,我不能直接去房地產公司買嗎?就沒有理會這些中介的話。

經過一番周折,小明發現房地產公司大多不負責直接賣房子,而是交給中介處理;還有流程確實很複雜:辦理買房手續,資格,以及貸款申請,資格審查。這些如果都是自己去做,不知道效率有多低。看來,“出門還得靠朋友”。好吧,小明只好去和中介周旋了。

其實,上面的例子,就是一個典型的代理模式在實際生活中的應用。在代理模式中,上面的角色也都有對應的表示。

2. 定義

上面說了那麼多,那麼什麼是代理模式呢?

代理模式,爲其它對象提供一種代理以控制對這個對象的訪問。

下面我們通過上面的例子,來引出代理模式中的角色。

中介在代理模式中是 Proxy 類,他有賣房子的功能;

房地產公司在代理模式中是RealSubject 類,他當然有賣房子的功能;

在程序中,兩個類有共同的功能,那麼我們就會把這個共同的功能抽取出一個接口來。這裏中介和房地產公司的共同功能是賣房子,現在我們抽取一個賣房子的接口,即Subject 接口。

中介和房地產公司之間的關係:中介必須要知道房地產公司,在程序中用關聯關係來表示。

中介賣的仍然是房地產公司的房子,真正賣房子的是房地產公司,所以我們說房地產公司是中介所代表的真實實體,也就是說 RealSubjectProxy 所代表的真實實體。

對於小明來說,只要跟中介打交道就行,不用再去跟房地產公司打交道了。

通過類圖,來說明一下:

  • Subject 接口,是抽象角色,是 RealSubjectProxy 的共同接口;
  • RealSubject 類,是真實角色,它實現了抽象角色接口。真實角色實現了真正的業務邏輯;
  • Proxy 類,是代理角色,它同樣實現了抽象角色接口。它是真實角色的代理,它保存了真實角色的引用。客戶調用 Proxy 類所實現的抽象角色接口,而在這個實現方法裏面,就調用真實角色的抽象方法實現。

3. 靜態代理

3.1 代碼演示

把上面場景用代碼實現一下,就是典型的靜態代理。

ASellHouseInterface 對應於抽象角色Subject 類:

/**
 * A 賣房接口
 */
public interface ASellHouseInterface {
    void sellAHouse(float size);
}

ARealEstateCompany 對應於真實角色RealSubject

/**
 * A 房地產公司
 */
public class ARealEstateCompany implements ASellHouseInterface {
    @Override
    public void sellAHouse(float size) {
        System.out.println("這是一套面積爲" + size + "平方的房子。");
    }
}

AMediation 對應於代理角色Proxy

/**
 * A 中介
 */
public class AMediation implements ASellHouseInterface {
    /**
     * 持有的真實角色對象引用
     */
    private ARealEstateCompany company;

    public AMediation(ARealEstateCompany company) {
        this.company = company;
    }
    /*前置處理器*/
    private void doSomethingBefore() {
        System.out.println("幫您分析買房需求,找到最適合您的房子。");
    }
    /*後置處理器*/
    private void doSomethingAfter() {
        System.out.println("幫您搞定繁瑣的貸款審批,讓您購房無憂!");
    }

    @Override
    public void sellAHouse(float size) {
        doSomethingBefore();
        company.sellAHouse(size);
        doSomethingAfter();
    }
}

測試代理如下:

public class HouseBuyer {
    public static void main(String[] args) {
        // 1, 靜態代理模式
        ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
        AMediation aMediation = new AMediation(aRealEstateCompany);
        aMediation.sellAHouse(150f);
    }
}
/*
打印結果:
幫您分析買房需求,找到最適合您的房子。
這是一套面積爲150.0平方的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!
 */

到這裏,我們總結一下靜態代理的步驟

  1. 定義真實角色和代理角色的共同接口,即抽象角色;
  2. 真實角色實現抽象角色的接口方法;
  3. 代理角色實現抽象角色的接口方法:在方法實現中,調用真實角色來完成業務邏輯。

3.2 不足之處

但是,靜態代理雖然很好地描述了上面的例子,但是它仍有些不足之處。
我們繼續上面的例子來說明:小花知道親愛的小明在看房子了,小花當然很開心,但是小花不想揹負太大的經濟壓力,所以小花重點關注了房價這個指標,她也找了相應的房地產公司以及賣房中介。
相應的類如下:
BSellHouseInterface 對應於抽象角色Subject 類:

/**
 * B 賣房接口
 */
public interface BSellHouseInterface {
    void sellBHouse(float price);
}

BRealEstateCompany 對應於真實角色RealSubject

/**
 * B 房地產公司
 */
public class BRealEstateCompany implements BSellHouseInterface {
    @Override
    public void sellBHouse(float price) {
        System.out.println("這是一套價值爲" + price + "萬的房子。");
    }
}

BMediation 對應於代理角色Proxy

/**
 * B 中介
 */
public class BMediation implements BSellHouseInterface {
    // 真實的對象
    private BRealEstateCompany company;

    public BMediation(BRealEstateCompany company) {
        this.company = company;
    }

    /*前置處理器*/
    private void doSomethingBefore() {
        System.out.println("幫您分析買房需求,找到最適合您的房子。");
    }
    /*後置處理器*/
    private void doSomethingAfter() {
        System.out.println("幫您搞定繁瑣的貸款審批,讓您購房無憂!");
    }
    @Override
    public void sellBHouse(float price) {
        doSomethingBefore();
        company.sellBHouse(price);
        doSomethingAfter();
    }
}

測試代碼如下:

public class HouseBuyer {
    public static void main(String[] args) {
        // 1, 靜態代理模式
        // 小明買房
        ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
        AMediation aMediation = new AMediation(aRealEstateCompany);
        aMediation.sellAHouse(150f);
        // 小花買房
        BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
        BMediation bMediation = new BMediation(bRealEstateCompany);
        bMediation.sellBHouse(30);
    }
}
/*
打印結果:
幫您分析買房需求,找到最適合您的房子。
這是一套面積爲150.0平方的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!
幫您分析買房需求,找到最適合您的房子。
這是一套價值爲30.0萬的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!
 */

有同學看到這裏,會想:這和小明找中介買房的代碼很相似,可以使用一套代碼來實現,一箇中介可以對應於多個開發商嘛。這樣可以實現一些代碼的複用。因爲如果都是一對一(一箇中介對應於一個房地產公司),那麼會出現很多代理對象,代碼量變大,可維護性差的問題。

代碼可以這樣寫:

ABMediation 是代理角色,它對應於 2 家房地產公司:ARealEstateCompanyBRealEstateCompany

/**
 * A & B 中介
 */
public class ABMediation implements ASellHouseInterface, BSellHouseInterface {
    // 真實的對象
    private ARealEstateCompany aCompany;
    private BRealEstateCompany bCompany;

    public ABMediation(ARealEstateCompany aCompany, BRealEstateCompany bCompany) {
        this.aCompany = aCompany;
        this.bCompany = bCompany;
    }

    /*前置處理器*/
    private void doSomethingBefore() {
        System.out.println("幫您分析買房需求,找到最適合您的房子。");
    }

    /*後置處理器*/
    private void doSomethingAfter() {
        System.out.println("幫您搞定繁瑣的貸款審批,讓您購房無憂!");
    }

    @Override
    public void sellAHouse(float size) {
        doSomethingBefore();
        aCompany.sellAHouse(size);
        doSomethingAfter();
    }

    @Override
    public void sellBHouse(float price) {
        doSomethingBefore();
        bCompany.sellBHouse(price);
        doSomethingAfter();
    }
}

下面是測試代碼:

public class HouseBuyer {
    public static void main(String[] args) {
        // 1, 靜態代理模式
        // 小明買房,一箇中介對應一個房地產公司(一對一)
        ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
        AMediation aMediation = new AMediation(aRealEstateCompany);
        aMediation.sellAHouse(150f);
        // 小花買房,一箇中介對應一個房地產公司(一對一)
        BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
        BMediation bMediation = new BMediation(bRealEstateCompany);
        bMediation.sellBHouse(30);
        // 小明,小花買房,一箇中介對應多個房地產公司(一對多)
        ABMediation abMediation = new ABMediation(aRealEstateCompany, bRealEstateCompany);
        abMediation.sellAHouse(180f);
        abMediation.sellBHouse(40);
    }
}
/*
打印結果:
幫您分析買房需求,找到最適合您的房子。
這是一套面積爲150.0平方的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!
幫您分析買房需求,找到最適合您的房子。
這是一套價值爲30.0萬的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!
幫您分析買房需求,找到最適合您的房子。
這是一套面積爲180.0平方的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!
幫您分析買房需求,找到最適合您的房子。
這是一套價值爲40.0萬的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!
 */

上面的方案,確實可以減少代理對象,但是卻違法了開閉原則—對於擴展開放(Open for extension),對於修改關閉(Closed for modification)。

因爲如果我們需要增加一家房地產公司 C,就不得不去修改 ABMediation 類;如果小明和小花的需求改變了,比如要買地段好的房子,那麼就需要修改接口,這樣也不得不修改 ABMediation 類。

而動態代理可以解決這些問題。

4. 動態代理

JDK 支持動態代理,需要使用java.lang.reflect 包下的 Proxy 類和 InvocationHandler 接口。

從這裏開始往下寫,我花了一天的工夫查看文檔,在想怎麼寫,因爲我本想採取一步一步引入的方式來介紹 JDK 中的動態代理,但是發現一天結束了我還是一頭霧水,不知道從哪裏開始說起(其實是我對動態代理的原理不理解)。是啊,對於 Java 瞭解淺薄的我,卻異想天開地打算從零推出 Java 設計者們設計精巧的動態代理實現,這多麼不契合實際。

我想了想,JDK 中的動態代理本身就是一種固定化的實現,我何不先照着 JDK 預定的方式實現動態代理,再去理解其中的設計呢?我希望自己能夠接近 Java 設計者的思想。

所以,我打算這樣分析:先直接照着文檔實現動態代理;然後再進行源碼方面的分析;最後,用自己的語言把動態代理的過程描述一下。

4.1 代碼演示

首先,定義MediationCompany 類,它是 InvocationHandler 接口的實現:

public class MediationCompany implements InvocationHandler {
    // 真實的對象
    private Object realEstateCompany;

    public void setRealEstateCompany(Object realEstateCompany) {
        this.realEstateCompany = realEstateCompany;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        doSomethingBefore();
        Object result = method.invoke(realEstateCompany, args);
        doSomethingAfter();
        return result;
    }

    /*前置處理器*/
    private void doSomethingBefore() {
        System.out.println("幫您分析買房需求,找到最適合您的房子。");
    }

    /*後置處理器*/
    private void doSomethingAfter() {
        System.out.println("幫您搞定繁瑣的貸款審批,讓您購房無憂!");
    }
}

MediationCompany 實現了 InvocationHandler 接口中的 invoke 方法,並且持有了真實對象的引用。在實現的 invoke 方法中,獲取到 Medthod 對象,通過反射調用了真實對象上的對應的方法,並返回了調用後的結果。

這裏需要說明一下,持有的真實對象的引用是 Object 類型的,這有什麼好處呢?好處是,如果現在要傳給 MediationCompany 另外一個真實對象(實現自不同的接口),就不需要更改 MediationCompany 的類結構了。

其次,看一下客戶端的代碼:

public class HouseBuyer {
    public static void main(String[] args) {
        // 2, 動態代理模式
        MediationCompany mediationCompany = new MediationCompany();
        // 小明買房
        ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
        mediationCompany.setRealEstateCompany(aRealEstateCompany);
        ASellHouseInterface consultant1 = (ASellHouseInterface) Proxy.newProxyInstance(
                aRealEstateCompany.getClass().getClassLoader(),
                new Class[] {ASellHouseInterface.class},
                mediationCompany);
        consultant1.sellAHouse(100f);
        // 小花買房
        BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
        mediationCompany.setRealEstateCompany(bRealEstateCompany);
        BSellHouseInterface consultant2 = (BSellHouseInterface) Proxy.newProxyInstance(
                bRealEstateCompany.getClass().getClassLoader(),
                new Class[] {BSellHouseInterface.class},
                mediationCompany);
        consultant2.sellBHouse(20);
    }
}

客戶端需要創建動態代理的實例,這需要藉助 Proxy 類的靜態方法 newProxyInstance

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
                               throws IllegalArgumentException

參數一:ClassLoader loader,需要一個類加載器,通常從已經被加載的對象中獲取其類加載器,傳遞給它就行;
參數二:Class<?>[] interfaces,就是這個代理類要實現的接口列表(這裏需要注意:是接口,而不是類或者抽象類);
參數三:InvocationHandler h,這是 InvocationHandler 的一個實現。因爲上面定義了 MedicationCompany 實現了 InvocationHandler 接口,所以我們傳遞一個 MediationCompany 對象即可。

注意到 newProxyInstance 方法的返回值是一個 Object 類型,所以我們需要把它的返回值強制類型轉換爲對應的公共接口,如 ASellHouseInterfaceBSellHouseInterface。這樣才能調用對應的接口方法。

我們運行一下上面的代碼,打印結果如下:

幫您分析買房需求,找到最適合您的房子。
這是一套面積爲100.0平方的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!
幫您分析買房需求,找到最適合您的房子。
這是一套價值爲20.0萬的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!

可以看到,動態代理的代碼完全實現了靜態代理的功能。

4.2 幾個疑問

但是,我們可不能到此爲止。

我們心中還有疑問:

  1. 爲什麼在客戶端調用動態代理對象的方法 consultant1.sellAHouse(100f); ,卻是在 MediationCompany 類實現的 invoke 方法輸出了結果?
  2. 上面明明創建了動態代理類:ASellHouseInterface consultant1BSellHouseInterface consultant2,但是爲什麼不能在 out 目錄下找到它們對應的 .class 文件?

關於問題1,我們在上面的代碼中添加一下日誌:

public class MediationCompany implements InvocationHandler {
    // 真實的對象
    private Object realEstateCompany;

    public void setRealEstateCompany(Object realEstateCompany) {
        this.realEstateCompany = realEstateCompany;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke(): proxy = " + proxy.getClass() + ", method = " + method + ", args = " + Arrays.toString(args));
        doSomethingBefore();
        Object result = method.invoke(realEstateCompany, args);
        doSomethingAfter();
        return result;
    }

    /*前置處理器*/
    private void doSomethingBefore() {
        System.out.println("幫您分析買房需求,找到最適合您的房子。");
    }

    /*後置處理器*/
    private void doSomethingAfter() {
        System.out.println("幫您搞定繁瑣的貸款審批,讓您購房無憂!");
    }
}
public class HouseBuyer {
    public static void main(String[] args) {
        // 2, 動態代理模式
        MediationCompany mediationCompany = new MediationCompany();
        // 小明買房
        ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
        mediationCompany.setRealEstateCompany(aRealEstateCompany);
        ASellHouseInterface consultant1 = (ASellHouseInterface) Proxy.newProxyInstance(
                aRealEstateCompany.getClass().getClassLoader(),
                new Class[] {ASellHouseInterface.class},
                mediationCompany);
        System.out.println("consultant1 = " + consultant1.getClass());
        consultant1.sellAHouse(100f);
        // 小花買房
        BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
        mediationCompany.setRealEstateCompany(bRealEstateCompany);
        BSellHouseInterface consultant2 = (BSellHouseInterface) Proxy.newProxyInstance(
                bRealEstateCompany.getClass().getClassLoader(),
                new Class[] {BSellHouseInterface.class},
                mediationCompany);
        System.out.println("consultant2 = " + consultant2.getClass());
        consultant2.sellBHouse(20);
    }
}

運行一下,查看打印結果:

consultant1 = class com.sun.proxy.$Proxy0
invoke(): proxy = class com.sun.proxy.$Proxy0, method = public abstract void com.java.advanced.features.proxy.ASellHouseInterface.sellAHouse(float), args = [100.0]
幫您分析買房需求,找到最適合您的房子。
這是一套面積爲100.0平方的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!
consultant2 = class com.sun.proxy.$Proxy1
invoke(): proxy = class com.sun.proxy.$Proxy1, method = public abstract void com.java.advanced.features.proxy.BSellHouseInterface.sellBHouse(float), args = [20.0]
幫您分析買房需求,找到最適合您的房子。
這是一套價值爲20.0萬的房子。
幫您搞定繁瑣的貸款審批,讓您購房無憂!

從上面的日誌,我們可以知道在客戶端動態代理實例 constultant1 調用了 consultant1.sellAHouse(100f);,在 MediationCompany 的 invoke 方法中就接收到了 method = public abstract void com.java.advanced.features.proxy.ASellHouseInterface.sellAHouse(float), args = [100.0],這兩者的信息是一模一樣的。這說明客戶端的動態代理實例調用 sellAHouse 方法,和 MediationCompnayinvoke 方法必然存在某種機制,把客戶端的動態代理實例的方法調用轉發給了 MediationCompany(一個InvocationHandler 接口的實現類) 的 invoke 方法來處理。不過,這只是初步的觀點,我們還需要查看源碼來驗證。

注意到,好事的我還打印了兩個動態代理實例對應的 Class 信息,以及 invoke 方法的第一個參數 Object proxy 對應的 Class 信息。截取出來如下:

consultant1 = class com.sun.proxy.$Proxy0
invoke(): proxy = class com.sun.proxy.$Proxy0
consultant2 = class com.sun.proxy.$Proxy1
invoke(): proxy = class com.sun.proxy.$Proxy1

可以看到,動態代理類被 invoke 方法給接收了。另外,也是很重要的一點,不知道你看出來沒有,動態代理類的名字怎麼這麼怪?它的路徑竟然不和程序中的包名一樣,名字裏還有一個 $ 符號。

我們或許打算在工程的編譯器輸出目錄下找有沒有 com.sun.proxy.$Proxy0.class的存在,因爲我們知道:Java 源文件(.java 文件)需要經過 javac 命令編譯成 Java 字節碼(.class 文件);Java 字節碼(.class 文件)經過 java 命令開始執行。那麼,我們有理由相信一定在某個地方存放着 com.sun.proxy.$Proxy0.class,不然不可能有對應的動態代理對象 consultant1。不過,在 out 目錄下有 MediationCompany.class,但是根本沒有 com.sun.proxy.$Proxy0.class,看下圖:

4.3 查看源碼

到這裏,如果不去查看源碼,還有什麼辦法解開我們的疑問呢?同時,我們也是帶着我們的疑問去查看源碼,這樣也更加有目標。
我們的問題目前有兩個:
1,動態代理實例(它是通過 Proxy.newProxyInstance 方法創建的)上的方法調用如何轉發給了 InvocationHandlerinvoke 方法?
2,動態代理類編譯好的 .class 文件在什麼地方存放?

這裏查看的是 JDK1.8 的源碼

Prxoy.newProxyInstance() 方法

Proxy 類的 newProxyInstance() 方法開始:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        // 省略校驗代碼部分
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        /*
         * Invoke its constructor with the designated invocation handler.
         */
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            // 省略了非關鍵的代碼部分
            return cons.newInstance(new Object[]{h});
  			// 省略了異常捕獲的部分
    }

這個方法接收了一個 ClassLoader 對象,一個代理要實現的接口列表,一個 InvocationHandler 實現類對象。
看一下 Class<?> cl = getProxyClass0(loader, intfs); 這行代碼的註釋:Look up or generate the designated proxy class(查找或生成指定的代理類),從裏我們可以推斷,這個方法就是用來獲取代理類的。這個方法需要兩個參數:一個 ClassLoader 對象,一個 InvocationHandler 實現類對象。
接着看 final Constructor<?> cons = cl.getConstructor(constructorParams); 這行代碼,這是獲取 Class<?> cl 的帶有參數的 Constructor 對象,其中 constructorParams 是:

  /** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

最後看 return cons.newInstance(new Object[]{h}); 這行代碼就是創建動態代理的實例了,通過反射調用動態代理類的帶有 InvocationHandler 類型參數的構造方法而創建的。

ProxygetProxyClass0() 方法

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // 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);
    }

自然,應該看 return proxyClassCache.get(loader, interfaces); 這行代碼,同樣地,不要視註釋而不見。我們把註釋翻譯一下:如果由給定的加載器定義的且實現了給定的接口的代理類存在,這裏就會直接返回緩存的副本;否則,這裏將通過 ProxyClassFactory 創建代理類。

劃一下重點啊:創建代理類要靠 ProxyClassFactory

ProxyClassFactoryProxy 類的靜態內部類。

ProxyClassFactory 類的 Class<?> apply(ClassLoader loader, Class<?>[] interfaces) 方法

		// prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
 public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            // 省略對 interfaces 進行校驗的 for 循環

            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
			// 省略處理 非 public 接口的 for 循環
            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                // 這裏 ReflectUtil.PROXY_PACKAGE 的值是"com.sun.proxy"
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            // 拼接處代理類的名字:"com.sun.proxy" + "$Proxy"+ long 型數字編號
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             * 生成指定的代理類
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
        }

defineClass0() 是一個本地方法,用來生成 Class 對象。我們無法查看 defineClass0() 的實現。但是,前面一行代碼:

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

是生成一個字節數組,這就是對應的.class 文件。

這裏可以回答第 2 個問題:上面明明創建了動態代理類:ASellHouseInterface consultant1BSellHouseInterface consultant2,但是爲什麼不能在 out 目錄下找到它們對應的 .class 文件?
因爲程序中默認並沒有生成對應的 .class 文件在磁盤上,而是在內存中直接使用,用於創建動態代理類。

我們可以把這個字節數組寫到本地來查看一下。

獲取生成的$Proxy.class文件

新建一個ProxyUtils類:

public class ProxyUtils {
	/**
	* 參數一:String proxyName,動態代理類的名字;
	* 參數二:Class<?>[] interfaces,代理類要實現的接口列表
	*/
    public static void generateClassFile(String proxyName, Class<?>[] interfaces) {
        /*
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);*/
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces);
        // 獲取當前的class文件輸出目錄
        String path = ProxyUtils.class.getResource(".").getPath();
        System.out.println(path);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(path + proxyName + ".class");
            fos.write(proxyClassFile);
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

HouseBuyer 類的 main 方法中添加代碼:

 ProxyUtils.generateClassFile(consultant1.getClass().getSimpleName(),
                new Class[]{ASellHouseInterface.class});
        ProxyUtils.generateClassFile(consultant2.getClass().getSimpleName(),
                new Class[]{BSellHouseInterface.class});
        for (Method method : aRealEstateCompany.getClass().getMethods()) {
            System.out.println(method.getName());
        }

運行代碼,打印結果如下:

/out/production/Java_01_AdvancedFeatures/com/java/advanced/features/proxy/dynamic/
/out/production/Java_01_AdvancedFeatures/com/java/advanced/features/proxy/dynamic/

在對應的目錄下,可以看到生成了兩個文件 $Proxy0.class$Proxy1.class

打開 $Proxy0.class,它就是 consultant1 這個動態代理類對應的字節碼文件:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.java.advanced.features.proxy.ASellHouseInterface;
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 implements ASellHouseInterface {
    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  {
        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 void sellAHouse(float var1) throws  {
        try {
            super.h.invoke(this, m3, 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"));
            m3 = Class.forName("com.java.advanced.features.proxy.ASellHouseInterface").getMethod("sellAHouse", Float.TYPE);
            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());
        }
    }
}

網上有人通過在 main 方法開始處,加入:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

這就打開了 ProxyGeneratorsaveGeneratedFiles 標記爲 true。
運行後,在 src 的同級目錄下,會出現一個 com.sun.proxy 包,這裏就是動態生成的代理類存放的地方。

分析生成的$Proxy0.class文件

看一下 $Proxy0 類:
首先,看類聲明,它繼承了 Proxy 類,並實現了 ASellHouseInterface 接口。這裏解釋了爲什麼在通過 Proxy.newProxyInstance() 方法獲取動態實例後,進行類型強制轉換爲 ASellHouseInterface 。有同學覺得應該轉型爲 ARealEstateCompany 的,可以看到明顯是不對的。

它有一個帶有 InvocationHandler 參數的構造方法,這就解釋了 Prxoy.newProxyInstance() 方法中對動態代理類獲取有 InvocationHandler 參數的 Constructor 對象,並調用其 newInstance() 方法(需要傳遞一個 InvocationHandler 對象)獲取動態代理實例。在構造方法內部,調用了父類 Proxy 的帶有 InvocationHandler 參數的構造方法,完成了對父類 Proxy 類的 protected InvocationHandler h; 賦值操作。

再往下看成員方法,有一個 sellAHouse(float val1) 的方法:

    public final void sellAHouse(float var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

這個方法就是我們通過 consultant1.sellAHouse(100f); 調用的。再說明一下:consultant1 是一個動態代理對象,對應的字節碼文件就是 $Proxy0.class。方法體內部,super.h.invoke(this, m3, new Object[]{var1}); 調用了 h 變量上的 invoke() 方法。h 是什麼呢?它是超類 Proxy 中的一個字段:

    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

而這個 InvocationHandler 對象就是實現了 InvocationHandler 接口的一個實現類對象,在程序中就是 MediationCompany 對象。我們是在 Proxy.newProxyInstance() 方法中傳入的 MediationCompany 對象。

那麼,super.h.invoke(this, m3, new Object[]{var1}); 這行代碼就是調用了 MediationCompany 類中的 invoke() 方法。第一個參數是 this,即動態代理類$Proxy0 的實例,第二個參數是 m3,是一個 Method 對象,它是在靜態代碼塊中早已賦值:

m3 = Class.forName("com.java.advanced.features.proxy.ASellHouseInterface").getMethod("sellAHouse", Float.TYPE);

m3 就是 sellAHouse 方法對應的 Method 實例。第三個參數是new Object[]{var1} 是參數列表。
到這裏,可以回答第 1 個問題:爲什麼在客戶端調用動態代理對象的方法 consultant1.sellAHouse(100f);,卻是在 MediationCompany 類實現的 invoke 方法輸出了結果?
在客戶端通過 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法創建了一個動態代理實例 consultant1,即 $Proxy0類的實例。而在 newProxyInstance 中傳入的第三個參數:InvocationHandler 對象(即 MediationCompany 對象),在通過反射調用 $Proxy0 類的帶 InvocationHandler 參數 構造方法時,交給 $Proxy0 對象持有。在調用 consultant1.sellAHouse(100f); 時,就是調用了 $Proxy0 中的 sellAHouse() 方法。在方法體內,調用了 $Proxy0 持有的 InvocationHandler 對象的 invoke 方法,當然就是調用 MediationCompany 中的 invoke 方法。

4.4 描述動態代理

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法中做了兩件事情:第一,通過前兩個參數生成動態代理類;第二,反射創建動態代理實例並把 InvocationHandler h 交給動態代理實例。
調用動態代理實例的方法,在方法內部,把業務處理交給自己持有的 InvocationHandler hinvoke 方法來處理。

動態代理的步驟:

  1. 定義代理角色和真實角色的公共接口;
  2. 真實角色實現公共接口中的方法;
  3. 定義一個實現了 InvocationHandler 的調用處理程序,它持有真實角色的引用;
  4. 在運行時通過 Proxy 類的 newProxyInstance 方法動態生成動態代理對象;
  5. 動態代理實例調用公共接口中的方法,轉發給第 3 步中的調用處理程序處理。
  6. 在調用處理程序的 invoke 方法中,調用真實角色的業務邏輯。

這裏是動態代理的類圖:
在這裏插入圖片描述

5. 靜態代理 vs 動態代理

優點 缺點
動態代理 1,代理類在程序運行時動態生成,減少了手動寫代理類的麻煩;2,可以在原始類和接口都未知的情況下,就確定代理類的代理行爲。 1,抽象角色只能是接口,不能是類或者抽象類;2,通過反射生成代理類並反射調用真實對象的方法,效率較低。
靜態代理 1,抽象角色可以是接口,類,抽象類;2,實現方式較爲簡單,沒有效率問題。 1,違反了開閉原則:接口改變,都不得不修改靜態代理的代碼;2,需要手動編寫代理類,有些麻煩。

6. 應用

對於靜態代理,可以查看Android-如何優雅的處理重複點擊
對於動態代理,可以查看 從動態代理角度看Retrofit,這纔是Retrofit的精髓!

代碼地址在https://github.com/jhwsx/Java_01_AdvancedFeatures/tree/master/src/com/java/advanced/features/proxy

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章