千山鸟飞绝,万径人踪灭。
孤舟蓑笠翁,独钓寒江雪
——唐·柳宗元《江雪》
首发于我的公众号
一、概述
最近在阅读retrofit源码时,有个关键的所在就是动态代理,细细回想了一下动态代理,发现之前有些细节还没有理解到位,本篇博文将重新深入理解一下动态代理。
二、关于代理
中华名族是一个含蓄的名族,讲究微妙和间接的交流方式。对象之间的间接通信也是同样是面向对象设计中一条重要的审美观,迪米特法则也指出“一个对象应该对其他对象保持最少的了解”,间接间通信可以达到“高内聚,低耦合”的效果。
代理是一种重要的手段之一,比如生活中的微商代理,厂家委托其代理销售商品,我们只跟微商打交道,不知道背后的“厂家是谁”,微商和厂家就可以抽象为 “代理类”和“委托类”,这样就可以,隐藏委托类的实现、实现客户与委托类之间的解耦,可以不用修改委托类的情况下做一些额外的处理
2.1、代理模式简介
在《Java与模式》一书中指出”代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的访问”,类图如下
通过类图可以发现,代理模式的代理对象Proxy
和目标对象Subject
实现同一个接口,客户调用的是Proxy
对象,Proxy
可以控制Subject
的访问,真正的功能实现是在Subject
完成的。
适用场景:
- 不希望某些类被直接访问。
- 访问之前希望先进行一些预处理。
- 希望对被访问的对象进行内存、权限等方面的控制。
优点如下
- 代理模式是通过使用引用代理对象来访问真实对象,在这里代理对象充当用于连接客户端和真实对象的中介者。
- 代理模式主要用于远程代理、虚拟代理和保护代理。其中保护代理可以进行访问权限控制。
2.2、静态代理
“静态”代理,若代理类在程序运行前已经存在,这种通常称为静态代理,比如微商A只代理A品牌的面膜,消费者通过微商才能买到某厂的面膜(控制权),其中微商和工厂都实现了了Sell的接口
委托类面膜工厂
1 | class FactoryOne :SellMask{ |
微商静态代理
1 | class BusinessAgent : SellMask { |
共同接口1
2
3interface SellMask {
fun sell()
}
2.3、相似模式的比较
既然获得引用就可以做一些扩展之类的事情,这点跟装饰者模式、适配器模式看起来很像,三者都属于结构型模式,但是代理模式核心是为其它对象提供一种代理以控制对这个对象的访问()
代理模式 VS 适配器模式
看上去很像,它们都可视为一个对象提供一种前置的接口,但是适配器模式的用意是改变所考虑的对象的接口,而代理模式并不能改变所代理的对象的接口,这一点上两个模式有着明显的区别,下图分别是 对象适配器和类的适配器UML图
代理模式VS 装饰模式
装饰者模式与所装饰的对象有着相同的接口,这一点跟代理模式相同,但是装饰模式更强调为所装饰的对象提供增强功能,而代理模式则是对对象的使用施加控制,并不提供对象本身的增强功能;被代理对象由代理对象创建,客户端甚至不需要知道被代理类的存在;被装饰对象由客户端创建并传给装饰对象。装饰者UML图如下
你以为这就完了吗?下面👇才是重头戏!
三、深入理解动态代理
3.1、什么是动态代理
代理类在程序运行时创建的代理方式被成为 动态代理。即代理类并不是在代码中定义的,而是在运行时根据我们在Java代码中”规定”的信息自动生成。静态代理容易造成代码的膨胀,。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。 还是以上面的卖面膜微商为例,她在进货市场进行一番比较之后再决定代理哪个品牌的面膜。
3.2、如何使用动态代理
跟上文一样,微商和面膜工厂都实现了sell接口,这里就不赘述了,下面看下与众不同的地方,实现动态代理需要实现InvocationHandler接口
实现InvocationHandler接口
1 | public class DynamicProxy implements InvocationHandler { |
- target 属性表示委托类对象
InvocationHandler
是负责连接代理类和委托类的中间类必须实现的接口。其中只有一个 invoke函数需要实现- invoke函数
1 | public Object invoke(Object proxy, Method method, Object[] args) |
下面好好看看这个核心函数的参数含义
- proxy 代通过dynamicproxy.newProxyInstance(business)自动生成的代理类 $Proxy0.class(下文会详细介绍)
- method表示代理对象被调用的函数,比如sellMask接口里面的sell方法
- args 表示代理大力调用函数的的参数,这里sell方法无参数
调用代理对象的每个函数,实际上最终都是走到InvocationHandler的invoke函数,因此可以在这里做一些统一的处理,AOP的雏形就慢慢出现了,我们也可以根据method方法名做一些判断,从而实现对某些函数的特殊处理。
使用动态代理
1 | fun main(args: Array<String>) { |
我们将委托类面膜工程FactoryMaskOne传到dynamicproxy.newProxyInstance中,通过下面的函数返回了一个代理对象1
2
3
4public Object newProxyInstance(Object object) {
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
实际代理类就是在这个时候动态生成的,后续调用到这个代理类的函数就会直接调用invoke函数,让我们细细看下这个Proxy.newProxyInstance
1 | public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) |
法的三个参数含义分别如下:
- loader:定义了代理类的ClassLoder;
- interfaces:代理类实现的接口列表
- h:调用处理器,也就是我们上面定义的实现了InvocationHandler接口的类实例
这里简单总结一下,委托类通过newProxyInstance 方法获取动态生成的代理类的实例(本例是$Proxy0.class),然后可以通过这个代理类实例调用代理的方法获得委托类的控制权,对代理类的调用实际上都会走到invoke方法,在这里我们调用委托类的相应方法,并且可以添加自己的一些逻辑,比如统一处理登陆、校验之类的。
3.3、动态生成的代理类$Proxy0
在Android Studio中调用不了ProxyGenerator
这个类,这个类在sun.misc包中,使用IntelliJ IDE创建java工程,需要看一下jdk的反射中Proxy和生成的代理类$Proxy0的源码,可以使用
1 | //生成$Proxy0的class文件 |
生成的代理类在com.sun.proxy包里面完整代码如下
1 | public final class $Proxy0 extends Proxy implements SellMask { |
从生成的代理类中可以看到
- 动态生成的代理类是以
$Proxy
为类名前缀,继承自Proxy
,并且实现了Proxy.newProxyInstance(…)
第二个参数传入的所有接口的类。 - 接口方法都交由h的invoke方法处理,h在父类Proxy中定义为InvocationHandler接口,为Proxy.newProxyInstance(…)的第三个参数
3.4 动态代理类如何生成
- 关注点1 Proxy.newProxyInstance(……)函数
动态代理类是在调用 Proxy.newProxyInstance(……)函数时生成的,精简后的核心代码如下
1 | public static Object newProxyInstance(ClassLoader loader, |
可以看到 首先调用它先调用getProxyClass(loader, interfaces)
得到动态代理类,然后将InvocationHandler
作为代理类构造函数入参新建代理类对象。
- 关注点2 Class<?> cl = getProxyClass0(loader, intfs);
如何获取到 生成动态代理类呢,一步步追踪,我们发现,在Proxy#ProxyClassFactory类中,在ProxyGenerator中去生成动态代理,类名以$Proxy+num作为标记
1 | /* |
小结
本篇主要java中的代理模式以及跟其他模式的对比,并重点介绍了JDK中的动态代理机制,像AOP、retrofit核心机制之一就使用到了这种技术,但Java动态代理是基于接口的,如果对象没有实现接口我们该如何代理呢?那就需要CGLIB了,CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理,这里就不展开赘述了。
参考链接
- https://juejin.im/post/5a99048a6fb9a028d5668e62
- https://juejin.im/post/5ad3e6b36fb9a028ba1fee6a
- http://www.cnblogs.com/xiaoluo501395377/p/3383130.html
- https://www.zhihu.com/question/20794107
- https://blog.csdn.net/qq_27095957/article/details/80184291
- http://a.codekk.com/detail/Android/Caij/公共技术点之 Java 动态代理
- https://www.jianshu.com/p/0391a8e93d3d
欢迎关注我的公众号,一起学习,共同提高~