动态代理
前言
众所周知,Retrofit
使用动态代理技术大大简化了 http 请求的过程,我们只需定义 http 请求相关的接口与接口方法,把定义的接口的 Class 对象传入 Retrofit#create()
方法即可,然后我们就可以直接调用接口方法发起 http 请求,对于如何发起 http 请求,请求过程是怎样的,数据如何解析完全无感知,非常棒的设计。
那么动态代理技术就必须学习一下了。既然有动态代理,那么必然就有静态代理,我们先学习下静态代理吧。
代理模式
代理模式在 Java 的设计模式中是比较常见的模式之一。代理模式比较简单,它是指客户端不直接或不能访问实际对象,而必须通过代理对象来间接访问实际对象。
在现实世界中,这种情况比较常见,比如我要买二手房,一般会先找个中介去和房东沟通,而我一般不直接和房东进行沟通;另外一个例子:小公司一般都没有专门的记账人员,会找一家代理记账的公司来为本公司记账。
静态代理
静态代理是我们手动编码实现的,在代码编译期就已经确认并完成代理。
静态代理的实现有模板步骤可以遵循:
- 首先定义一个接口并定义一个具体实现,
- 其次构建一个代理对象并实现上一步定义的接口,
- 最后将被代理的对象注入进代理对象中,然后在代理对象的方法内调用被代理对象对应的方法。
通过以上三步模板步骤,我们就可以实现屏蔽被代理对象与客户端之间的直接联系。
下面我们以购买二手房为例来看看静态代理吧。
Talk
接口
1.定义interface Talk {
String speak();
void listen(String msg);
}
二手房交易就是买方与卖方之间通过中介进行沟通,所以我们定义一个 Talk
接口,接口内有讲话 speak()
和收听他人的讲话 listen()
两个方法。
Talk
接口
2.买方实现 class Buyer implements Talk {
@Override
public String speak() {
return "出价2000万, 你问他卖不卖";
}
@Override
public void listen(String msg) {
System.out.println("买方听到: " + msg);
}
}
买方实现 Talk
接口,并说 “出价2000万, 你问他卖不卖”,并等待卖方的回复。
Talk
接口
3.卖方实现 class Seller implements Talk {
@Override
public String speak() {
return "少于2001万不卖";
}
@Override
public void listen(String msg) {
System.out.println("卖方听到: " + msg);
}
}
卖方实现 Talk
接口,收到买方的问话,2000万就想买我的房子,太看不起我了,并回复 “少于2001万不卖”。
Talk
接口
4.中介实现 class Proxy implements Talk {
private final Talk buyer;
public Proxy(Talk buyer) {
this.buyer = buyer;
}
@Override
public String speak() {
String speak = buyer.speak();
String proxy = "买方说:" + speak;
System.out.println(proxy);
return "中介说: 您好,哥, 刚才买方出价2000万了, 这个价格差不多了";
}
@Override
public void listen(String msg) {
buyer.listen(msg);
}
}
- 中介收到买方的出价后,并稍加修饰去询问卖方,等待卖方的回复,
- 中介收到卖方的回复后,没有修饰直接转给了买方。
5.实际调用
public static void main(String[] args) {
Talk buyer = new Buyer();
Talk proxy = new Proxy(buyer);
Talk seller = new Seller();
String buyerSpeak = proxy.speak();
seller.listen(buyerSpeak);
String sellerSpeak = seller.speak();
proxy.listen(sellerSpeak);
}
运行上述代码,可以看到以下输出:
买方说:出价2000万, 你问他卖不卖
卖方听到: 中介说: 您好,哥, 刚才买方出价2000万了, 这个价格差不多了
买方听到: 少于2001万不卖
从以上代码的输出结果可以看出,买方和卖方之间没有直接的沟通,都是通过中介在中间来回传话,当然现实世界不会如此简单,可能卖方也有中介,那就是买方和买方的中介沟通,然后中介和卖方的中介沟通,最后卖方的中介再和卖方沟通。
静态代理一般是定义接口,我们知道在接口中新增接口方法,所有的实现类都要实现新增的接口方法,所以静态代理的劣势很明显,不利于后续的维护,而且不够灵活,因此动态代理应运而生。
动态代理
与静态代理相比,动态代理比较灵活。我们不需要编写代理对象的代码,即动态代理不处于编译期,而是处于运行时动态的创建代理对象。
动态代理的实现方式有很多种,常用的有 JDK 动态代理、CGLIB 动态代理等,由于 Retrofit
中使用的 JDK 动态代理,所以我们现在只学习 JDK 动态代理,其他动态代理的实现方式在学习完 Retrofit
后再了解下吧。
既然静态代理有模板步骤可以遵循,那么动态代理也是有模板步骤的:
- 在静态代理中的接口和被代理对象实现不变,
- 构建一个调用处理器并实现
InvocationHandler
接口, - 通过调用
java.lang.reflect.Proxy
的newProxyInstance()
方法来动态创建代理对象, - 最后在调用处理器的
invoke()
方法中处理代理逻辑。
InvocationHandler
public interface InvocationHandler {
/**
* 当你调用代理对象的方法的时候实际上会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
invoke
方法有三个参数:
- proxy : 动态代理创建的代理对象,
- method : 调用的代理对象方法,
- args : 调用的代理对象方法的参数。
动态代理创建的代理对象对于开发者无感知,所以 JDK 为开发者提供了一个 InvocationHandler
接口,这样当调用代理对象方法时,内部通过调用 InvocationHandler
的 invoke
接口方法,这样我们开发者就可以做一些逻辑处理了。
Proxy#newProxyInstance()
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}
newProxyInstance()
方法需要三个参数:
- loader : 类加载器,用于加载创建出来的代理对象,
- interfaces : 被代理对象实现的接口,
- h: 调用处理器,即实现
InvocationHandler
接口的对象。
接下来让我们使用动态代理改写前面的购买二手房示例吧。
动态代理
首先之前定义的 Talk
接口和买卖双方的实现不变,我们直接定义调用处理器:
class DynamicProxy implements InvocationHandler {
// 被代理对象
private final Talk target;
public DynamicProxy(Talk target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法名称
String name = method.getName();
// 根据方法名称做不同的处理
if (name.equals("speak")) {
Object invoke = method.invoke(target, args);
// 增加前缀 'D'
String busySpeak = "D 买方说:" + invoke;
System.out.println(busySpeak);
return "D 中介说: 您好,哥, 刚才买方出价2000万了, 这个价格差不多了";
} else if (name.equals("listen")) {
// 增加前缀 'D'
args[0] = "D " + args[0];
return method.invoke(target, args);
}
return method.invoke(proxy, args);
}
}
- 首先实现
InvocationHandler
接口并在构造方法中注入被代理对象, - 其次实现
invoke
接口方法,在invoke
方法内处理逻辑, - 获取此时代理对象被调用的方法名称,根据方法名称做不同的处理,
- 我们在
invoke
方法中讲的话前面都加了字符 'D' 来区分是静态代理还是动态代理。
下面我们修改调用处的代码:
public static void main(String[] args) {
Talk buyer = new Buyer();
Talk seller = new Seller();
- // Talk proxy = new Proxy(buyer);
+ ClassLoader classLoader = buyer.getClass().getClassLoader();
+ Class<?>[] interfaces = buyer.getClass().getInterfaces();
+ DynamicProxy dynamicProxy = new DynamicProxy(buyer);
+ Talk proxy = (Talk) java.lang.reflect.Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);
String buyerSpeak = proxy.speak();
seller.listen(buyerSpeak);
String sellerSpeak = seller.speak();
proxy.listen(sellerSpeak);
}
在调用处,我们注释掉之前静态代理创建代理对象的逻辑,增加创建动态代理对象的逻辑。
运行以上代码,可以看到以下输出:
D 买方说:出价2000万, 你问他卖不卖
卖方听到: D 中介说: 您好,哥, 刚才买方出价2000万了, 这个价格差不多了
买方听到: D 少于2001万不卖
从以上代码的输出结果看出,动态处理创建的代理对象确实生效了。
总结与思考
- 静态代理实现起来比较简单,且易于理解,但是后续维护成本较高,
- 动态代理实现起来比较复杂,需要掌握一些反射知识,学习曲线较为陡峭,但是它比较灵活,后续维护成本一般,
静态代理和动态代理各有优劣,需要我们在实际编码过程中做好取舍,笔者个人比较喜爱静态代理,上手比较快,代码一目了然,当静态代理满足不了需求时,再考虑动态代理。
有了动态代理的前置知识,想必我们再学习 Retrofit
时就不会那么吃力了。
希望可以帮你更好的使用 Retrofit
,happy ~~