一 代理 Proxy
代理在我们的生活中无处不在。例如小周忙于工作,把找对象委托给“婚恋中介”,亦或是将游戏委托给“游戏代练”,让“钟点工”帮忙打扫房子。
以上出现的“婚恋中介”,“游戏代练”,“钟点工”都是某种意义上的代理。代理的好处是帮助你做一些你不想做或者不能做的事情。例如,你没有时间自己去认识美丽女孩儿👧🏻,你自己不想清理脏脏的马桶🚽,有了代理,你可以委托他们来帮你做这样的事情。
在实际项目中,代理的作用是什么呢? 同样,在客户端不想或不能直接引用一个对象时,代理可以帮助其间接地去引用一个对象。代理对象是”真实对象(realObject)“ 和 ”目标对象(target Object) “的一个中介者,将他们俩联系起来。
例如你在隧道的地铁中,由于信号的紊乱,你此时只能预览高清图的缩略图。[1]
小图片便是大图片的代理。
亦或是本地需要访问远程服务器内存中的某个对象,本地是无法直接访问的,这时可以建立一个远程代理,帮助本地访问远程内存中的对象。
总结:代理是某个对象的替代对象,通过引用代理或控制代理,实现对真实对象的引用。
例如《红楼梦》中,赵姨娘扎小人让贾宝玉和王熙凤患病。赵姨娘便是通过宝玉和熙凤的代理(傀儡小人)来控制他们姐弟。
二 代理模式
2.1 模式结构
因为Proxy需要能够反映真实对象的性质,所以代理类和真实类需要有相同的接口。例如赵姨娘扎小人需要贴上宝玉和熙风的生辰八字,生辰八字便是傀儡小人与他姐弟二人的共同接口。
- Subject: 代理和真实对象共同接口 (生辰八字)
- Client: 客户端 (赵姨娘)
- Proxy: 代理对象 (傀儡小人)
- RealSubject: 真实对象 (贾宝玉、王熙凤)
由于赵姨娘是贾政的妾室,没法直接对宝玉和熙风下手,因此她聪明地采用了代理模式作案。
2.2 简化代码
public class Proxy implements Subject { // 实现共同接口
private RealSubject realSubject = new RealSubject();
public void preRequest(){...}
// 只要调用代理对象的request()方法,不仅实现了调用真实对象的request(), 还实现了增强功能
public void request(){
preRequest();
realSubjectRequest();
postRequest();
}
public void preRequest(){...}
}
2.3 代理模式的优缺点
优点
(1)代理模式通过协调客户端和真实对象,降低了系统的耦合度。
(2)远程代理:使客户端可以访问远程机器上的对象,远程机器可能性能更好,提高了我们的办公效率。
(3)虚拟代理:利用小的对象来代理大的对象,减小了对系统资源的消耗,可以提高运行速度。
(4)保护代理:控制客户端对真实对象的使用权限,避免重要信息泄露。
缺点
(1) 由于代理的存在,会增加一步代理的步骤,若代理产生的优化还不如代理产生的延迟,反而会聪明反被聪明误 make yourself the victim of your own success。
(2) 实现代理很麻烦的!!!
2.4 常见代理
(1) 远程代理
远程代理有时被叫做 Ambessador, 即大使。中国驻某国领事馆的大使便是我们外交部的远程代理,协助国内外交部解决外交事务。远程代理可以让位于不同物理内存中的对象取得联系。
(2)虚拟代理
虚拟即virtual,利用小的资源代替大的资源。
(3)保护代理
控制客户对真实对象的访问,提供了不同的权限。例如会员制度便是通过代理模式实现,在代理类中,增加不同的访问等级,给用户提供不同的使用权限。
(4)防火墙代理
因为代理并不是真实对象本身,这样可以避免真实对象被攻击。例如张庭的传销公司便采用了防火墙代理,持股人不是她自己,她通过代理操控公司,面对法律追责时则可以规避风险。
(5)缓冲代理
为一个请求的结果提供临时储存的空间,下次其他请求便可以共享这个请求的结果。
三 动态代理
动态代理的典型应用是Spring AOP。
传统的静态代理,客户端通过代理对象ProxySubject来调用真实对象RealSubject中的request()方法。这种模式必须是真实对象实现已经存在的。但如果每个真实对象都要有一个自己的代理类来实现代理,那么代理类将会急剧增加,我们实现代理和后期维护代理会非常困难。那么,有没有一个通用的代理类呢?
Java为我们提供了java.refelct.lang包,使得我们可以在事先不知道真实对象的情况下,对真实对象进行代理,这就是动态代理。
接下来,我们一起看看java的 动态代理是怎么实现的吧!
InvocationHandler接口
public interface InvocationHandler {
/**
* @ proxy : 代理类
* @ method : 需要代理的方法
* @ args: method方法的参数数组
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
动态代理类需要实现InvocationHandler接口,实现invoke()方法,以此来调用真实对象的方法,并对真实对象的方法进行增强(即增加功能)。
Proxy类
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
客户端通过Proxy类的静态方法,newProxyInstance(), 运行时动态地识别需要代理哪个类。
动态代理使得灵活性更好了,是多态(polymorphism)的一种体现。
3.1 动态代理两种方式
(1)JDK Proxy
JDK Proxy是java的默认代理方式,最大特点是需要被代理类具有要被实现的接口。见 target.getClass().getInterfaces()
public class JDKProxy implements InvocationHandler {
private Object target;
public JDKProxy(Object target) {
this.target = target;
}
public <T> T getProxy(){
// 传入真实类的类加载器,真实类的接口,JDKProxy
return (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this)
}
}
(2) CGLib (Code Generation Library)
如果某个类没有接口,那么就无法使用JDK Proxy了。 CGLIB,顾名思义,是一个可以生产code的库,可以在运行时扩展Java类和实现Java接口。
CGLib会动态查找类中没有被final修饰的public方法,将其转换为字节码,然后将字节码转化为相应的代理对象class文件。
[1] 参考文献:《Java设计模式》第二版,刘伟。清华大学出版社,248-258。