代理模式以及在Android中的使用

本文涉及java中的一些反射知識,若是你對反射這部分不太熟悉,建議先去了解一下反射知識,或者看一下個人這篇文章 Java反射以及在Android中的使用javascript

代理模式

1、定義

某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。php

舉例說明:css

代理模式從字面上就是咱們理解的生活中那個中介代理,好比公司A(原對象)爲海外公司,消費者B(某一個對象)直接從公司A購買商品須要各類各樣複雜的步驟,這時候就出現了代理人C(代理對象),讓他來替咱們去處理那些複雜的步驟,咱們只須要告訴代理人C咱們須要什麼商品就能夠了,由代理人C去跟公司A進行購買,消費只須要等着收快遞,其餘的不用關心。java

2、使用代理好處

一、經過引入代理對象的方式 來間接訪問目標對象,防止直接訪問目標對象給系統帶來的沒必要要複雜性; python

二、經過代理對象對原有的業務加強;android

3、UML圖

公共接口角色:定義了委託角色代理角色的共同接口或者抽象類。git

委託角色(公司A) :實現或者繼承抽象主題角色,定義實現具體業務邏輯的實現。github

代理角色(代理C) : 實現或者繼承抽象主題角色,持有委託角色的引用,控制和限制委託角色的實現,而且擁有本身的處理方法(預處理和藹後)。web

特色:數組

  • 委託角色代理角色共同繼承同一個接口或者實現抽象類

  • 代理角色持有委託角色對象的引用

4、靜態代理

根據上面的例子,咱們來實現一個靜態代理:

靜態代理在使用時,須要先定義接口或者抽象類,委託類代理類一塊兒實現相同的接口或者是繼承相同抽象類。通常來講,委託類代理類是一對一的關係,固然一個代理對象對應多個委託類也是能夠的。

步驟 :定義公共接口——>建立委託類(公司A)——>建立代理類(代理C)——>訪問(消費者執行)

一、定義公共接口

/**
 * @author : EvanZch
 * description: 抽象主題角色,商家和代理銷售這個操做
 **/

public interface IFactoryA {
    void saleManTools(String size);
}
複製代碼

二、委託類 (公司A)

/**
 * @author : EvanZch
 *         description: 公司A, 實現IFactory
 **/

public class FactoryA implements IFactoryA {
    /**
     * 實現具體的業務邏輯
     *
     * @param size
     */

    @Override
    public void saleManTools(String size) {
        System.out.println("公司A——>出貨:" + size + "MM");
    }
}
複製代碼

三、代理類 (代理C)

/**
 * @author : EvanZch
 * description: 代理C,實現IFactory,並持有真實對象FactoryA引用
 **/

public class ProxyC implements IFactoryA {

    private IFactoryA factory;
    /**
     * 持有被代理角色的引用
     * @param iFactory
     */

    public void setFactory(IFactoryA iFactory) {
        this.factory = iFactory;
    }

    /**
     * 對真實對象方法進行加強
     * @param size
     */

    @Override
    public void saleManTools(String size) {
        doBefore();
        factory.saleManTools(size);
        doAfter();
    }

    private void doBefore() {
        System.out.println("代理C——>根據客戶需求定製方案");
    }

    private void doAfter() {
        System.out.println("代理C——>收集使用反饋");
    }
}
複製代碼

在執行被代理類方法前,能夠進行功能拓展,符合開閉原則。

四、消費者

/**
 * @author : EvanZch
 * description: 消費者B
 **/

public class ConsumerB {
    public static void main(String[] args) {
        IFactoryA factoryA = new FactoryA();
        ProxyC proxyC = new ProxyC(factoryA);
        proxyC.saleManTools("36D");
    }
}
複製代碼

結果:

若是這個時候出現了專賣女性用品的公司B,咱們須要按照下面步驟再走一遍

定義公共接口——>建立委託類——>建立代理類——>訪問

優勢:

靜態代理好處就是能對目標對象方法進行功能拓展。

上面例子中能夠看到,海外公司只負責發貨,代理類能夠在不改動委託類的狀況下對目標人羣進行需求方案定製和使用狀況反饋收集工做,對委託類的方法進行了功能拓展。

缺點:

靜態代理,一對一(一個代理只代理一個公司)則會出現代理對象量多、代碼量大,從而致使代碼複雜,可維護性差的問題,一對多(一個代理代理多個公司)則代理對象會出現擴展能力差的問題。

能夠看到咱們公共接口是銷售男性用品,若是後續需求增長女性用品是否是又要改動接口或者增長接口?接口一改動,代理類也要跟着改,牽一髮而動全身,當需求愈來愈多愈來愈複雜的時候,就會使整個代碼臃腫,而且維護性變差。

5、動態代理

使用動態代理沒必要要本身在去實現代理類,只須要一個動態代理類 (代理公司) 就可讓程序運行在期間動態的建立接口的實現。

動態代理實現的關鍵是須要使用 InvocationHandler接口和經過 Proxy 類動態建立對象。

步驟 :定義公共接口——>建立委託類(公司A)——>只須要建立一個動態代理類(代理公司)——>訪問(消費者執行)

一、InvocationHandler 接口

處理動態代理類對象方法的調用,每一個動態代理類都會關聯一個。

package java.lang.reflect;

/**
 * {@code InvocationHandler} is the interface implemented by
 * the <i>invocation handler</i> of a proxy instance.
 *
 * <p>Each proxy instance has an associated invocation handler.
 * When a method is invoked on a proxy instance, the method
 * invocation is encoded and dispatched to the {@code invoke}
 * method of its invocation handler.
 *
 * @author      Peter Jones
 * @see         Proxy
 * @since       1.3
 */

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable
;
}
複製代碼

二、Proxy類

用來建立代理對象的類,是全部動態代理類的父類,主要使用 newProxyInstance 方法

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

複製代碼

先貼動態代理類實現的代碼:

/**
 * @author : EvanZch
 *         description: 動態代理類,必須實現InvocationHandler接口
 **/

public class ProxyCompany implements InvocationHandler {
    /**
     * 依舊持有真實對象
     */

    private Object mFactory;

    public void setFactory(Object factory) {
        this.mFactory = factory;
    }

    /**
     * 獲取動態代理對象
     */

    public Object getDynamicProxy() {
        /**
         * 拿到動態代理對象
         * ClassLoader loader :真實對象的ClassLoader
         * Class<?>[] interfaces : 真實對象實現的接口
         * InvocationHandler h  : InvocationHandler對象
         */

        return Proxy.newProxyInstance(mFactory.getClass().getClassLoader()
                , mFactory.getClass().getInterfaces(), this);
    }

    /**
     * InvocationHandler 接口方法
     *
     * @param proxy  代理類自己
     * @param method 咱們所要調用某個對象真實的方法的 Method 對象
     * @param args   method 對象中自己須要傳入的參數
     * @return
     * @throws Throwable
     */

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        doBefore();
        // 調用真實對象方法
        Object result = method.invoke(mFactory, args);
        doAfter();
        return result;
    }

    private void doBefore() {
        System.out.println("代理公司——>方案制定");
    }

    private void doAfter() {
        System.out.println("代理公司——>收集反饋");
    }
}
複製代碼

調用動態代理

        // 建立動態代理對象
        ProxyCompany proxyCompany = new ProxyCompany();
        // 公司A
        IFactoryA factoryA = new FactoryA();
        // 動態代理引入真實對象
        proxyCompany.setFactory(factoryA);
        // 動態的建立代理類
        IFactoryA proxyA = (IFactoryA) proxyCompany.getDynamicProxy();
        proxyA.saleManTools("F");
複製代碼

結果:

仍然獲取正確結果,縱觀整個過程,咱們並無實質的建立一個代理類,整個過程只須要一個動態代理類就能完成,若是這個時候咱們出現了公司B,咱們只須要執行的步驟 定義公共接口——>建立委託類(公司B)——>訪問(消費者執行) 具體代碼就不貼了,跟公司A相似,我把調用代碼貼在一塊兒,你品,細細的品。

        // 建立動態代理對象
        ProxyCompany proxyCompany = new ProxyCompany();
        // 公司A   IFactoryA:公共接口   FactoryA:委託類(公司A)
        IFactoryA factoryA = new FactoryA();
        // 動態代理引入真實對象
        proxyCompany.setFactory(factoryA);
        // 動態的建立代理類
        IFactoryA proxyA = (IFactoryA) proxyCompany.getDynamicProxy();
        proxyA.saleManTools("F");

        // 公司B   IFactoryB:公共接口   FactoryB : 委託類(公司B)
        IFactoryB factoryB = new FactoryB();
        proxyCompany.setFactory(factoryB);
        IFactoryB proxyB = (IFactoryB) proxyCompany.getDynamicProxy();
        proxyB.saleWomanTool(180);
複製代碼

結果:

不知道各位看官可有一點感悟,咱們能夠看到,就算這個時候出現了公司B,咱們整個過程也沒有建立真實的代理對象,而是直接經過一個動態代理類中 proxyCompany.getDynamicProxy() 來動態的獲取咱們的代理類對象。既然是動態代理,那代理類確定存在,只是jdk動態的給咱們生成,那真實的代理類是誰?怎麼建立的?

咱們先來看代理類是誰這個問題,咱們對剛剛的代碼進行debug調試

從日誌輸入信息裏面能夠看到,jdk爲在運行時分別給咱們的 IFactoryAIFactoryB 生成了名字爲$Proxy0$Proxy1 的代理類,那它在哪產生的?帶着這個問題,咱們開始深刻。

6、源碼分析

咱們是經過 這個方法來獲取動態代理對象,那咱們從這裏切入,先看看newProxyInstance作了啥?

若是你對反射熟悉的話,圖片中標註的幾處你應該很容易知道在幹嗎。

A:獲取類的Class對象

B:反射獲取其構造方法

C:遍歷構造賦予權限

D:返回該類的實例

這裏出現的類,沒的說,確定就是咱們須要的那個實際的代理類,咱們再看一下 getProxyClass0 方法作了啥?

感受貼圖比貼代碼舒服,就直接貼圖了,這個方法很簡單,先對接口數目進行判斷,65535這個數字搞Android的就很熟悉了吧,方法數不能超過65535,這不是咱們本文討論的關鍵,關鍵看 proxyClassCache.get(loader, interfaces)

咱們看到這個方法傳入了咱們設置的 ClassLoader參數 和 interfaces 參數 進入 get 方法裏面

咱們看到標識這裏,傳入了咱們的參數,應該是一行關鍵性代碼,咱們再看它作了啥?

能夠看到它是一個接口方法,這個時候咱們須要再去找方法的實現類

能夠看到 apply 方法實現類有 KeyFactoryProxyClassFactory 聰明你的看名字也應該知道,咱們只須要關注 ProxyClassFactory 這個類

image
image

這裏隱隱約約出現了 proxyName, 還記得咱們前面debug調試出現的 $Proxy0$Proxy1嗎?

private static final String proxyClassNamePrefix = "$Proxy";
String proxyName = proxyPkg + proxyClassNamePrefix + num;
複製代碼

看到 $Proxy 了嗎? 原來名字就是從這裏出來的

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
複製代碼

這裏經過ProxyGenerator類生成指定的代理類字節數組。

其實jdk生成.class文件的內容就是一串串字節數組

defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
// 這是一個 native 方法
private static native Class<?> defineClass0(ClassLoader loader, String name,
 byte[] b, int off, int len);
複製代碼

再經過 defineClass0方法 經過指定ClassLoader生成代理類的Class對象,到這裏,咱們前面關於動態產生的代理類怎麼產生的問題也就解決了。

既然 ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags) 方法生成指定代理類的字節數組,那咱們能夠經過這個方法來看看,具體內容是啥?

咱們經過 generateProxyClass 獲取到字節數組,並保存到本地。

    public static void generateProxyClass(String proxyName, Class clazz{
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces(), 1);
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(proxyClassFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
複製代碼

將咱們動態產生的類,用上面的方法保存到本地。

generateProxyClass(proxyA.getClass().getSimpleName(), factoryA.getClass());
generateProxyClass(proxyB.getClass().getSimpleName(), factoryB.getClass());
複製代碼

能夠看到生成的兩個class文件,名字恰好跟咱們前面debug看到的一直,咱們反編譯文件看一下里面是啥。

idea 打開.class文件自動進行反編譯。

$Proxy0

public class $Proxy0 extends Proxy implements IFactoryA {
    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})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void saleManTools(String 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)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals"new Class[]{Class.forName("java.lang.Object")});
            m3 = Class.forName("com.evan.proxy.staticProxy.IFactoryA").getMethod("saleManTools"new Class[]{Class.forName("java.lang.String")});
            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());
        }
    }
}
複製代碼

關鍵信息:

一、 $Proxy0 繼承至 Proxy

二、實現了 IFactoryA 接口

三、實現了咱們接口裏面的方法 :saleManTools(String var1)

能夠看到咱們動態產生的代理類 $Proxy0 繼承至 Proxy ,前面也說過,Proxy 是全部動態代理類的父類,全部動態代理類都須要繼承它,經過生成的文件,咱們能夠證明這點。

前面提到代理模式特色一直就是 代理類和委託類要同時實現一個接口或者實現抽象類,這裏能夠看到,咱們建立的動態代理類一樣也實現了咱們的 IFactoryA 接口。

咱們再看一下saleManTools方法的實現

    public final void saleManTools(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
複製代碼

關鍵信息:

super.h.invoke(this, m3, new Object[]{var1});

這個 h 是啥?在動態產生的代理類 $Proxy0 沒看到這個參數,咱們再在其父類 Proxy 中查看

image
image

就是咱們前面動態代理類中實現的 InvocationHandler 接口。因此在 saleManTools 方法中,再調用了 InvocationHandler 接口的 invoke 方法,咱們再回憶一下前面寫動態代理類時候,怎麼處理invoke方法的?回憶不起來,我就再貼一次!

    /**
     * InvocationHandler 接口方法
     *
     * @param proxy  代理類自己
     * @param method 咱們所要調用某個對象真實的方法的 Method 對象
     * @param args   method 對象中自己須要傳入的參數
     * @return
     * @throws Throwable
     */

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        doBefore();
        // 調用真實對象方法
        Object result = method.invoke(mFactory, args);
        doAfter();
        return result;
    }
複製代碼

再看 saleManTools 方法實現中的這行,你對比着看,你品,細細的品。

super.h.invoke(this, m3, new Object[]{var1});
複製代碼

那這裏的m3不就是Method嘛

m3 = Class.forName("com.evan.proxy.staticProxy.IFactoryA").getMethod("saleManTools"new Class[]{Class.forName("java.lang.String")});
複製代碼

再看m3的值,不就是經過反射拿到 咱們本身定義的IFactoryA 接口的 saleManTools 方法???看到這裏,再回頭去看看咱們前面的動態代理類,你對 InvocationHandlerProxy 這兩個關鍵類應該就有了更清晰的認識了,若是沒有,就再看一遍??

好了,以上源碼分析內容基本就是jdk對靜態代理的實現(這個車是否是剎的有點快,哈哈)。

7、動態代理在Android中的運用

retrofit 這個網絡請求庫我相信搞Android的大哥們應該都用過吧,咱們通常怎麼操做?

一、編寫 xxxApi 接口

public interface xxxApi {
     String HOST = "xxxxxxxxxx";
     @POST("app/xxxx")
     @FormUrlEncoded
     Observable<BaseResponse<String>> sendEmailCode(@Field("email") String email);
}
複製代碼

二、初始化 retrofit :

Retrofit retrofit = new Retrofit.Builder()
 .baseUrl("https://xxxxxx")
 .build();
複製代碼

三、動態建立 xxxApi 實例

xxxApi service = retrofit.create(xxxApi.class);
service.sendEmailCode(xxxx);
複製代碼

有沒有很熟悉,咱們 create 的時候不是隻傳了一個 interface 進去嗎?怎麼就能夠直接經過返回的實例調用方法了呢?跟咱們前面的動態代理是否是有幾分類似?咱們去看看Retrofit的源碼,看他 Create 到底操做了啥?

咱們在github上能夠看到 retrofit 的 create 方法

image
image

看到 Proxy.newProxyInstance 這個方法,就應該很清楚了,證明咱們前面的猜想,至於具體怎麼操做的跟前面的相似,這裏就再也不分析了。

相關文章
相關標籤/搜索