android常用设计模式之代理设计模式及动态代理原理

定义:代理模式属结构型设计模式。为其他对象提供一种代理以控制对这个对象的访问。

代理模式结构图

代理类结构图.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处, 我们输出了动态代理为我们生成的代理类对象类型。
执行单元测试后结果如下:


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容