android常用設計模式之代理設計模式及動態代理原理

zyl409214686發表於2018-01-09

定義:代理模式屬結構型設計模式。為其他物件提供一種代理以控制對這個物件的訪問。

代理模式結構圖

2229793-94c73efde4c1b7d5
代理類結構圖.jpg

在代理模式中有如下角色:

  • ISubject: 抽象主題類,宣告真實主題與代理的共同介面方法。
  • RealSubject:真實主題類,代理類所代表的真實主題。客戶端通過代理類間接地呼叫真實主題類的方法。
  • Proxy:代理類,持有對真實主題類的引用,在其所實現的介面方法中呼叫真實主題類中相應的介面方法執行。

1. 簡單實現程式碼

public interface IShop {
    void buy();
}

public class BuyProxy implements IShop {

    private IShop mShop;

    public BuyProxy(IShop shop){
        mShop = shop;
    }

    @Override
    public void buy() {
        mShop.buy();
    }
}

public class Customer implements IShop {
    @Override
    public void buy() {
        System.out.print("顧客購物");
    }
}

public class BuyProxyTest {
    private Customer mCustomer;
    private BuyProxy mBuyProxy;
    @Before
    public void setUp() throws Exception {
        mCustomer = new Customer();
        mBuyProxy = new BuyProxy(mCustomer);
    }

    @Test
    public void buy() throws Exception {
        mBuyProxy.buy();
    }

}

2.動態代理

從編碼角度來說,代理模式分為靜態代理和動態代理。上面的例子是靜態代理,在程式碼執行前就已經存在了代理類的class編譯檔案;而動態代理則是在程式碼執行時通過反射來動態生成類並確定代理誰。Java提供動態代理介面InvocationHandler,實現該介面需要重寫invoke方法。
下面用動態代理實現上面的例子,程式碼如下:

//動態代理類
public class DynamicBuyProxy implements InvocationHandler {
    IShop shop;

    public DynamicBuyProxy setShop(IShop shop) {
        this.shop = shop;
        return this;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object resutl = method.invoke(shop, args);
        if("buy".equals(method.getName())){
            System.out.println("---通過動態代理購買---");
        }
        return null;
    }
}

//動態代理模式單元測試類
public class DynamicBuyProxyTest {
    DynamicBuyProxy mDynamicProxy;
    IShop mCustomer;
    ClassLoader mClassLoader;
    @Before
    public void setUp() throws Exception {
        mCustomer = new Customer();
        mDynamicProxy = new DynamicBuyProxy();
        mDynamicProxy.setShop(mCustomer);
        mClassLoader = mCustomer.getClass().getClassLoader();
    }

    @Test
    public void invoke() throws Exception {
        IShop proxyer = (IShop) Proxy.newProxyInstance(mClassLoader, new Class[]{IShop.class}, mDynamicProxy);
        proxyer.buy();
        System.out.println("proxyer.classname:"+proxyer.getClass().getName()); //Attend01
    }
}

注意上面程式碼註釋Attend01處, 我們輸出了動態代理為我們生成的代理類物件型別。
執行單元測試後結果如下:


2229793-842977fb0054804b.png
dynamicproxy.png

意料之中的是代理類正常的輸出了我們想要的代理類邏輯。
而代理類型別卻出乎我們意料com.sun.proxy.$Proxy5,從這裡引出它的原理。

原理

實際上通過 Proxy.newProxyInstance 建立的代理物件是在jvm執行時動態生成的一個物件,它並不是我們的InvocationHandler型別,也不是我們定義的那組介面的型別,而是在執行是動態生成的一個物件,並且命名方式都是這樣的形式,以$開頭,proxy為中,最後一個數字表示物件的標號。
下面來看它的原始碼:

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 IShop {  
    private static Method m1;  
    private static Method m0;  
    private static Method m3;  
    private static Method m2;  
  
    static {  
        try {  
            m1 = Class.forName("java.lang.Object").getMethod("equals",  
                    new Class[] { Class.forName("java.lang.Object") });  
            m0 = Class.forName("java.lang.Object").getMethod("hashCode",  
                    new Class[0]);  
            m3 = Class.forName("com.zyl.designpatterns.structuralpatterns.proxy.IShop").getMethod("buy",  
                    new Class[0]);  
            m2 = Class.forName("java.lang.Object").getMethod("toString",  
                    new Class[0]);  
        } catch (NoSuchMethodException nosuchmethodexception) {  
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
        } catch (ClassNotFoundException classnotfoundexception) {  
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
        }  
    }  
  
    public $Proxy0(InvocationHandler invocationhandler) {  
        super(invocationhandler);  
    }  
  
    @Override  
    public final boolean equals(Object obj) {  
        try {  
            return ((Boolean) super.h.invoke(this, m1, new Object[] { obj }))  
                    .booleanValue();  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    @Override  
    public final int hashCode() {  
        try {  
            return ((Integer) super.h.invoke(this, m0, null)).intValue();  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    @Override  
    public final String toString() {  
        try {  
            return (String) super.h.invoke(this, m2, null);  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    @Override  
    public void buy() {  
        try {  
            super.h.invoke(this, m3, null);  //Attend02
            return;  
        } catch (Error e) {  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
  
    }  
}  

可以看到上面程式碼註釋Attend02中h實際上就是我們的InvocationHandler介面的實現類DynamicBuyProxy,呼叫它的invoke方法就是呼叫了我們InvocationHandler.invoke()方法。
是不是豁然開朗了,實際上它就是JVM為我們生成了一個代理類,靜態代理是我們編譯之前寫好的, 而動態代理是由JVM根據我們提供的介面為我們動態生成的。

場景

是不是感覺用它的地方不多呢,但是實際上動態代理場景有很多,比如Spring的核心AOP、Android最近大火的Retrofit等等。

優點

  • 真實主題類就是實現實際的業務邏輯,不用關心其他的非本職工作。
  • 任何主題類隨時都會該發生變化,但是因為它實現了公共介面,所以代理類可以不做任何修改就能夠使用。

如果對動態代理的作用還是比較模糊, 建議看看這篇知乎的解答Java 動態代理作用是什麼?

程式碼已上傳github

相關文章