Java代理模式分析总结

动机

学习动机来源于RxCache,在研究这个库的源码时,被这个库的设计思路吸引了,该库的原理就是通过动态代理和Dagger的依赖注入,实现Android移动端Retrofit的缓存功能。

既然在项目中尝试使用这个库,当然要从设计的角度思考作者的思路,动态代理必然涉及到Java的反射,既然是反射,性能当然会有所降低,那么是否有更好的思路呢,使用动态代理的优势有哪些?

关于动态代理,百度上面的资料数不胜数,今天也借鉴其他前辈的学习总结,自己实践一次代理的实现。

静态代理

代理分为动态代理和静态代理,我们先看静态代理的代码:

我们首先定义一个接口:

public interface Subject {

    void enjoyMusic();

}

我们接下来实现一个Subject的实现类:

public class RealSubject implements Subject {

    @Override
    public void enjoyMusic() {
        System.out.println("enjoyMusic");
    }
}

在不考虑代理模式的情况下,我们调用Subject的真实对象,我们代码中必然是这样:

    @Test
    public void testNoProxy() throws Exception {
        Subject subject= new RealSubject();
        subject.enjoyMusic();
    }

上面是我们的业务代码,我们这样使用当然没有问题,但是我们需要考虑的一点是,如果我们的业务代码中多次引用了这个类,并且在之后的版本迭代中,我们需要修改(或者替换)这个类,我们需要在引用这个对象的代码处进行修改——也就是说我们需要修改业务代码。

这显然不是良好的设计,我们希望业务代码不需要修改的前提下,进行RealSubject的修改(或者替换),这时我们可以通过代理模式,创建一个代理类,从而达到控制RealSubject对象的引用 :

public class SubjectProxy implements Subject {
    private Subject subject = new RealSubject();
    @Override
    public void enjoyMusic() {
        subject.enjoyMusic();
    }
}

我们在业务代码中通过代理类,达到调用真实对象RealSubject的对应方法:

@Test
public void staticProxy() throws Exception {
    SubjectProxy proxy = new SubjectProxy();

    proxy.enjoyMusic();
}

这就是静态代理,优势是显然的,如果我们需要一个新的对象NewRealSubject代替RealSubject 应用在业务中,我们不需要修改业务代码,而是只需要在代理类中,将代码进行简单的替换:

private Subject subject = new RealSubject();//before

private Subject subject = new NewRealSubject();//after

同理,即使RealSubject类有所修改(比如说构造函数添加新的参数依赖),我们也不需要在每一处业务代码中添加一个新的参数,只需要在代理类中,对代理的真实对象进行简单修改即可。

瑕疵

现在我们看到了静态代理的优势,但是还有一点需要我们去思考,随着项目中业务量的逐渐庞大,真实对象类的功能可能越来越多:

//接口类
public interface Subject {

    void enjoyMusic();

    void enjoyFood();

    void enjoyBeer();
    
    //...甚至更多
}

//实现类
public class RealSubject implements Subject {

    @Override
    public void enjoyMusic() {
        System.out.println("enjoyMusic");
    }

    @Override
    public void enjoyFood() {
        System.out.println("enjoyFood");
    }

    @Override
    public void enjoyBeer() {
        System.out.println("enjoyBeer");
    }

    //...甚至更多
}

这样岂不是说明,我们的代理类也要这样:

public class SubjectProxy implements Subject {

    private Subject subject = new RealSubject();

    @Override
    public void enjoyMusic() {
        subject.enjoyMusic();
    }

    @Override
    public void enjoyFood() {
        subject.enjoyFood();
    }

    @Override
    public void enjoyBeer() {
        subject.enjoyBeer();
    }
    
    //...甚至更多
}

静态代理的话,确实如此,随着真实对象的功能增多,不可避免的,代理对象的代码也会随之臃肿,这是我们不希望看到的,我们更希望的是,即使真实对象的代码量再繁重,我们的代理类也不要有太多的改动和臃肿。

动态代理

直接来看代码,我们首先声明一个接口和实现类:

public interface Subject {

    void enjoyMusic();

    void enjoyFood();

    void enjoyBeer();
}

public class RealSubject implements Subject {

    @Override
    public void enjoyMusic() {
        System.out.println("enjoyMusic");
    }

    @Override
    public void enjoyFood() {
        System.out.println("enjoyFood");
    }

    @Override
    public void enjoyBeer() {
        System.out.println("enjoyBeer");
    }
}

这两位是老朋友了,接下来我们实现一个动态代理类:

public class DynamicProxy implements InvocationHandler {

    private Object subject;

    public DynamicProxy(Object subject) {
        this.subject = subject;
    }

    public Object bind() {
        return Proxy.newProxyInstance(subject.getClass().getClassLoader(),
                subject.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        method.invoke(subject, args);
        return null;
    }
}

我们来看业务代码:

@Test
public void dynamicProxy() throws Exception {
    RealSubject realSubject = new RealSubject();
    Subject proxy = (Subject) new DynamicProxy(realSubject).bind();

    System.out.println(proxy.getClass().getName());

    proxy.enjoyMusic();
    proxy.enjoyFood();
    proxy.enjoyBeer();
}

动态代理中,我们可以看到,最重要的两个类:
Proxy 和 InvocationHandler
接下来分别对这两个类的功能进行描述.

InvocationHandler接口

我们先看一下Java的API文档:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
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 invoke method of its invocation handler.

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

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

proxy:  指代最终生成的代理对象
method:  指代调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数

我们看到,我们的动态代理类中,实现了InvocationHandler接口的invoke方法,这里面三个参数的作用很简单,以proxy.enjoyMusic();为例,参数1表示最终生成的代理对象,参数2表示enjoyMusic()这个方法对象,参数3代表调用enjoyMusic()的参数,此例中是没有调用参数的。

有一个疑问是,这个proxy对象是什么呢,是这个new DynamicProxy(realSubject)吗?

当然不是,我们可以看到,这个代理对象proxy实际上是调用bind()方法获得的,也就是说是通过这个方法获得的:

Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(),
this);

Proxy类

先看JavaAPI:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

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

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

  • loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
  • interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
  • h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

可以看到,Proxy这个类才会帮助我们生成相应的代理类,它是如何知道我们需要生成什么类的代理呢?

看第二个参数,我们把Subject.class作为参数传进去时,那么我这个代理对象就会实现了这个接口,这个时候我们当然可以将这个代理对象强制类型转化,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

我们看到,我在业务代码中对生成的proxy进行了打印:

System.out.println(proxy.getClass().getName());

//结果:
//$Proxy0

也就是说,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

到此,动态代理的基本知识就告一段落了。

参考资料:

java的动态代理机制详解:(对我帮助很大,衷心感谢!)
http://www.cnblogs.com/xiaoluo501395377/p/3383130.html

知乎:Java 动态代理作用是什么?
https://www.zhihu.com/question/20794107

本文sample代码已托管github:

https://github.com/qingmei2/Samples-Android/tree/master/SampleProxy/app/src/test/java/com/qingmei2/sampleproxy

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

推荐阅读更多精彩内容

  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李头阅读 3,181评论 2 10
  • 1 场景问题# 1.1 访问多条数据## 考虑这样一个实际应用:要一次性访问多条数据。 这个功能的背景是这样的;在...
    七寸知架构阅读 2,890评论 1 52
  • 本篇文章继续介绍Java反射机制,不同的是侧重于介绍动态代理。动态代理是代理模式中的一种,是通过Java反射机制来...
    Android进阶与总结阅读 567评论 0 0
  • 冬日的夜晚 风潇潇的吹着 几颗星零散 挂在天上 树啸惊魂 好冷 一颗流星倏忽而逝 降落在无际的宇宙里 只留下一道长...
    六月天气阅读 208评论 22 27
  • 然则轮回有情如何生起迷乱? 本始基清净境界为无明所障,即成阿赖耶,是为“痴”之自性;于“痴”之境界中,业力发动,此...
    Zhenzen阅读 184评论 0 0