在 代理模式:女朋友这么漂亮,你缺经纪人吗? 中我们用宝强的例子介绍了静态代理模式的概念。
本来我的目的是通过大家耳熟能详的例子来加深理解,但是有些网友指责我“没底线”、“幸灾乐祸”,其实我比你们谁都爱宝强!他的每个电影我都看,电影里他受苦了我都心疼,比如说看到《盲井》里弱小的他被失足女欺负,我只想说:放开宝强,换我来!
OK,这篇文章我们将结合其他例子介绍动态代理模式。: )
回顾静态代理
为了加深理解我们回顾一下静态代理,定义一个规定行为的明星电影接口 IMovieStar :
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
再定义一个 IMovieStar 的实现类 Star :
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
最后定义个代理类 Agent,它引用了一个 Star 对象,并且对 Star 的行为进行了控制:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
最后进行单元测试:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
运行结果:
可以看到,被代理类 Star 只需要完成自己的功能,不用因为业务逻辑而频繁修改代码,取而代之的是用 Agent 来做中间人,由它来代替 Star完成一些业务操作。
静态代理弊端
我们可以看到,
- 我们需要在运行前手动创建代理类,这意味着如果有很多代理的话会很累哎;
- 其次代理类 Agent 和 被代理类 Star 必须实现同样的接口,万一接口有变动,代理、被代理类都得修改,容易出问题。
主角出场:动态代理
动态代理 与 静态代理 最大的区别就是不用我们创建那么多类,敲那么多代码。在程序运行时,运用反射机制动态创建而成。
JDK 中为我们提供了 Proxy 类来实现动态代理,其中最重要的方法是 newProxyInstance:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
参数介绍:
- ClassLoader loader // 被代理类的类加载器,用来创建代理类
- Class<?>[] interfaces //被代理类实现的接口,创建的代理类会实现这些接口
- InvocationHandler invocationHandler //最关键的接口!它只有一个 invoke 方法,是代理类进行 拦截操作 的入口,一般需要自定义一个 Handler 来实现方法增强。
举个栗子
我们自定义一个 Handler 来实现上述静态代理例子中 经纪人对片酬的控制:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
可以看到,我们可以在 invoke 方法中,根据 method 的名称、创建类等信息进行相应的拦截、处理。
注意! 在 ProxyHandler 中我们创建了 getProxy() 方法,这个方法用于调用 Proxy.newProxyInstance(…) 方法生成代理类。
单元测试:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
走两步:
可以看到,使用时只需要传入明星即可,以后即使这个 IMovieStar 接口修改,也不会影响到经纪人。
除此以外,即使这个明星新增了其他功能,经纪人也不必修改太多。比如黄渤早年其实是个歌手,唱歌不得志只好去演戏,成为影帝后人们才关注他的歌声(真是个“看脸、看名”的世界):
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
要使用明星的唱歌功能,就要返回一个 ISingerStar 类型的经纪人,这里
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
运行结果:
案例浅析:Retrofit 中的动态代理
我们知道,Retrofit 是使用 依赖注入+ 动态代理,其中动态代理的入口就是这句:
- 1
- 2
- 1
- 2
我们进去一探究竟:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
我们可以看到,自定义的请求接口中的每个方法都会被拦截,然后根据方法声明类、调用平台等筛选要控制的方法,最后将要进行网络请求的方法进行转换、适配,最后返回一个网络请求代理类。
了解动态代理后,再看这段代码不是那么吃力了吧!Retrofit 我们暂且了解到这,等掌握更多设计模式、网络基础后再进行具体的解析。
总结
上篇文章通过明星与经纪人的关系介绍了静态代理,不好之处在于一个经纪人只能代理一个明星,一旦明星有变动,或者想要代理其他明星时,需要修改、创建经纪人,大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;
而动态代理模式,做到了”一个经纪人代理 N 个明星“,大大减少类的创建、修改成本。此外动态代理还符合 AOP (面向切面编程) 思想,在很多场合都有使用。
由 Proxy 类的静态方法创建的动态代理类具有以下特点:
- 动态代理类是* public* 、final 和非抽象类型的;
- 动态代理类继承了 Java.lang.reflect.Proxy 类;
- 动态代理类的名字以 “$Proxy” 开头;
- 动态代理类实现 newProxyInstance() 方法中参数 interfaces 指定的所有接口;
JDK 动态代理的实现方式:
1. 自定义实现 InvocationHandler
2. 根据方法信息进行拦截、控制
3. 调用时传入代理对象
4. 根据要使用方法决定返回代理类的类型
代码地址点这里
备注
本文所讨论的动态代理实现方式是使用 JDK 提供的 Proxy 类,这个类只支持对接口实现类的代理,这在有些场景下会有约束。
针对这种情况,有人创建了 CGLIB (Code Generation Library) 开源项目,它补充了JDK 动态代理仅支持接口实现类的不足:
CGLIB 是一个强大的高性能的代码生成包。广泛的被许多AOP 的框架使用,例如 spring AOP 和 dynaop ,为他们提供方法的 interception(拦截)。 – 百度知道
有兴趣的同学可以去了解一下
(PS : 我是一名 Android 开发一年半经验的小菜鸟,由于意识到自己只会调用 API 而不了解底层原理,所以设定了一系列进阶计划并定期更新,其中包括设计模式、网络基础、多线程、注解、框架解析、性能优化、JVM 深入理解等内容,欢迎持续关注我的博客 http://blog.csdn.NET/u011240877,我们一起进步!
感谢
- http://rejoy.iteye.com/blog/1627405
- http://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html
- http://wl9739.github.io/2016/07/23/%E7%BB%86%E8%81%8A%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/
- http://blog.csdn.net/zhuyu714997369/article/details/52023664
- http://zhidao.baidu.com/link?url=sh6dXxXOxOog52SPG6luMIXhR66dd4i6mOJsBN_q9j9oWwu9qsBw5QWkWLTrjBsGS5-3y5GwgOhd1sCq2uMLlq