代理模式(静态、动态、retrofit实例)
某些情况下,我们不希望或是不能直接访问对象 A,而是通过访问一个中介对象 B,由 B 去访问 A 达成目的,这种方式我们就称为代理。
对象 A 所属类称为委托类 / 被代理类,对象 B 所属类称为代理类。
代理的使用场景:
-
远程代理:
为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。 -
虚拟代理:
通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。 -
缓冲代理:
为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。 -
保护代理:
可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。 -
智能引用:
要为一个对象的访问(引用)提供一些额外的操作时可以使用
代理的特点:
面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。
- 代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
- 代理类的对象与一个委托类的对象关联,但是代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
代理的优点:
- 隐藏委托类的实现
- 解耦,不改变委托类代码情况下做一些额外处理,比如添加初始判断及其他公共操作
根据程序运行前代理类是否已经存在,可以将代理分为静态代理和动态代理。
1、静态代理
由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
有一个公共接口(Subject),一个具体的类(RealSubject),一个代理类(ProxySubject),代理类持有具体类的实例,代为执行具体类实例方法。
//公共接口
public interface Subject {
void doSomthing(String sth);
}
//委托类,实现公共接口,是实际干活的
public class RealSubject implements Subject {
@Override
public void doSomthing(String sth) {
System.out.println("委托类doSomthing : " + sth);
}
}
//代理类,实现公共接口,持有委托类的对象引用,间接调用委托类的相关方法
public class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
System.out.println("代理类实例化,代理类持有委托类的对象引用");
this.realSubject = realSubject;
}
@Override
public void doSomthing(String sth) {
System.out.println("代理类间接调用委托类的相关方法");
realSubject.doSomthing(sth);
}
}
//测试类
public class StaticProxyTest {
public static void main(String[] args) {
//创建委托类,真正干活的
RealSubject realSubject = new RealSubject();
//创建代理类,代理类持有委托类的对象引用,间接调用委托类的相关方法
ProxySubject proxySubject = new ProxySubject(realSubject);
//操作代理类
proxySubject.doSomthing("洗衣服");
}
}
2、动态代理
代理类在程序运行时创建的代理方式被成为动态代理。
没有手动写代理类,需要实现InvocationHandler (负责连接代理类和委托类的中间类,整个代理的逻辑处理都在自定义的InvocationHandler实现类中进行处理的),然后通过Proxy.newProxyInstance()来生成代理类。
- 动态代理要求委托类必须实现某个接口(Retrofit没有委托类,只有接口)
- 可以指定代理实现多个接口 (new Class[] { interface1, interface2 })
- 可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。如果需要对某个方法进行自定义逻辑处理,可以根据method的特征信息进行判断分别处理。
//公共接口
public interface Operate {
void doSomething(String sth);
}
//委托类,实现公共接口,是实际干活的
public class RealOperate implements Operate {
@Override
public void doSomething(String sth) {
System.out.println(this.getClass().getSimpleName() + " doSomething " + sth);
}
}
//负责连接代理类和委托类的中间类
public class OperateInvocation implements InvocationHandler {
private Object operate;
public OperateInvocation(Object operate) {
this.operate = operate;
}
/**
* 调用代理对象的每个函数实际最终都会调用InvocationHandler的invoke函数,自定义代理逻辑处理
* @param proxy 通过 Proxy.newProxyInstance() 动态生成的代理类对象
* @param method 被代理对象调用的函数
* @param args 被代理对象调用的函数的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理执行前夕");
Object obj = method.invoke(operate, args);
System.out.println("动态代理执行完成");
return obj;
}
}
//测试类
//代理类是不是同一个实例,是与传入OperateInvocation构造函数中的委托类相关的
public class DynamicTest {
public static void main(String[] args) {
//创建代理类proxyOperate
Operate proxyOperate = (Operate) Proxy.newProxyInstance(Operate.class.getClassLoader(),
new Class[] {Operate.class}, new OperateInvocation(new RealOperate2()));
System.out.println("代理类proxyOperate:" + proxyOperate.getClass()); //打印出代理类名
proxyOperate.doSomething("做饭");
}
}
生成的代理类:class com.sun.proxy.$Proxy0
只保留关键代码
//动态生成的代理类,继承自Proxy,实现了公共接口
public final class $Proxy0 extends Proxy implements Operate
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
//动态生成的代理类持有InvocationHandler 的引用
public $Proxy0(InvocationHandler paramInvocationHandler) throws {
super(paramInvocationHandler);
}
//通过反射得到method实例
static {
try{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.zhen.proxy.Operate").getMethod("doSomething", Class.forName("java.lang.Object"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
} //省略catch抛异常的部分
}
public final void doSomething(String sth) throws {
try {
//通过InvocationHandler 的引用,回调invoke(proxy, method, args)方法
//也验证了前面说的三个入参分别是:代理类对象、代理对象调用的方法、代理对象调用的方法的参数
this.h.invoke(this, m3, sth);
return;
} //省略catch抛异常的部分
}
//省去toString,hashCode、equals方法的内容。
}
- $Proxy0 extends Proxy implements Person,代理类继承自Proxy类,所以也就决定了java动态代理只能对接口进行代理(无法实现多继承)。
- jdk生成的这个$Proxy0类是在内存中的,Proxy.newProxyInstance()创建代理对象时,通过反射获得这个类的构造方法,然后创建的代理实例(里面做了缓存,没有就创建代理类,有就从缓存里取)。
- 代理类$Proxy0持有InvocationHandler的实例,在调用方法时执行InvocationHandler.invoke(),InvocationHandler持有委托类的实例,method.invoke(target, args)。即代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行委托对象(被代理对象)的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
- 动态代理实现实际是双层的静态代理,开发者提供了委托类 B,程序动态生成了代理类 A。开发者还需要提供一个实现了InvocationHandler的子类 C,子类 C 连接代理类 A 和委托类 B,它是代理类 A 的委托类,委托类 B 的代理类。用户直接调用代理类 A 的对象,A 将调用转发给委托类 C,委托类 C 再将调用转发给它的委托类 B。
关于Retrofit的动态代理解析:
1. 创建一个Retrofit实例(构造者模式)
2. 创建一个接口,并通过动态代理得到接口的实例
3. 请求网络接口请求网络接口
retrofit = Retrofit.Builder().baseUrl("url").build() //创建retrofit实例
apiService = retrofit.create(ApiService.class); //得到动态代理apiService(动态代理实现了接口)
apiService.login(userName, password); //通过动态代理进行网络请求
- 动态代理相当于一个拦截器,接口中的所有方法都会走InvocationHandler中的invoke()
(因为生成的动态代理类会持有InvocationHandler的引用,在每个方法中执行h.invoke(this, method, args)) - 动态获取接口的注解、入参、返回值(封装网络请求,获取解析返回值)
- 没有委托类
参考:
10分钟看懂动态代理设计模式
23种设计模式----------代理模式(三) 之 动态代理模式
java动态代理实现与原理详细分析
秒懂Java代理与动态代理模式
公共技术点之 Java 动态代理