跳至主要內容

动态代理

guodongAndroid大约 8 分钟

前言

众所周知,Retrofit 使用动态代理技术大大简化了 http 请求的过程,我们只需定义 http 请求相关的接口与接口方法,把定义的接口的 Class 对象传入 Retrofit#create() 方法即可,然后我们就可以直接调用接口方法发起 http 请求,对于如何发起 http 请求,请求过程是怎样的,数据如何解析完全无感知,非常棒的设计。

那么动态代理技术就必须学习一下了。既然有动态代理,那么必然就有静态代理,我们先学习下静态代理吧。

代理模式

代理模式在 Java 的设计模式中是比较常见的模式之一。代理模式比较简单,它是指客户端不直接或不能访问实际对象,而必须通过代理对象来间接访问实际对象。

在现实世界中,这种情况比较常见,比如我要买二手房,一般会先找个中介去和房东沟通,而我一般不直接和房东进行沟通;另外一个例子:小公司一般都没有专门的记账人员,会找一家代理记账的公司来为本公司记账。

proxy

静态代理

静态代理是我们手动编码实现的,在代码编译期就已经确认并完成代理。

静态代理的实现有模板步骤可以遵循:

  1. 首先定义一个接口并定义一个具体实现,
  2. 其次构建一个代理对象并实现上一步定义的接口,
  3. 最后将被代理的对象注入进代理对象中,然后在代理对象的方法内调用被代理对象对应的方法。

通过以上三步模板步骤,我们就可以实现屏蔽被代理对象与客户端之间的直接联系。

下面我们以购买二手房为例来看看静态代理吧。

1.定义Talk接口
interface Talk {

    String speak();

    void listen(String msg);
}

二手房交易就是买方与卖方之间通过中介进行沟通,所以我们定义一个 Talk 接口,接口内有讲话 speak() 和收听他人的讲话 listen() 两个方法。

2.买方实现 Talk 接口
class Buyer implements Talk {

    @Override
    public String speak() {
        return "出价2000万, 你问他卖不卖";
    }

    @Override
    public void listen(String msg) {
        System.out.println("买方听到: " + msg);
    }
}

买方实现 Talk 接口,并说 “出价2000万, 你问他卖不卖”,并等待卖方的回复。

3.卖方实现 Talk 接口
class Seller implements Talk {

    @Override
    public String speak() {
        return "少于2001万不卖";
    }

    @Override
    public void listen(String msg) {
        System.out.println("卖方听到: " + msg);
    }
}

卖方实现 Talk 接口,收到买方的问话,2000万就想买我的房子,太看不起我了,并回复 “少于2001万不卖”。

4.中介实现 Talk 接口
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);
    }
}
  1. 中介收到买方的出价后,并稍加修饰去询问卖方,等待卖方的回复,
  2. 中介收到卖方的回复后,没有修饰直接转给了买方。
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 后再了解下吧。

既然静态代理有模板步骤可以遵循,那么动态代理也是有模板步骤的:

  1. 在静态代理中的接口和被代理对象实现不变,
  2. 构建一个调用处理器并实现 InvocationHandler 接口,
  3. 通过调用 java.lang.reflect.ProxynewProxyInstance() 方法来动态创建代理对象,
  4. 最后在调用处理器的 invoke() 方法中处理代理逻辑。

InvocationHandler

public interface InvocationHandler {

    /**
     * 当你调用代理对象的方法的时候实际上会调用到这个方法
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

invoke 方法有三个参数:

  1. proxy : 动态代理创建的代理对象,
  2. method : 调用的代理对象方法,
  3. args : 调用的代理对象方法的参数。

动态代理创建的代理对象对于开发者无感知,所以 JDK 为开发者提供了一个 InvocationHandler 接口,这样当调用代理对象方法时,内部通过调用 InvocationHandlerinvoke 接口方法,这样我们开发者就可以做一些逻辑处理了。

Proxy#newProxyInstance()

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    ......
}

newProxyInstance() 方法需要三个参数:

  1. loader : 类加载器,用于加载创建出来的代理对象,
  2. interfaces : 被代理对象实现的接口,
  3. 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);
    }
}
  1. 首先实现 InvocationHandler 接口并在构造方法中注入被代理对象,
  2. 其次实现 invoke 接口方法,在 invoke 方法内处理逻辑,
  3. 获取此时代理对象被调用的方法名称,根据方法名称做不同的处理,
  4. 我们在 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万不卖

从以上代码的输出结果看出,动态处理创建的代理对象确实生效了。

总结与思考

  1. 静态代理实现起来比较简单,且易于理解,但是后续维护成本较高,
  2. 动态代理实现起来比较复杂,需要掌握一些反射知识,学习曲线较为陡峭,但是它比较灵活,后续维护成本一般,

静态代理和动态代理各有优劣,需要我们在实际编码过程中做好取舍,笔者个人比较喜爱静态代理,上手比较快,代码一目了然,当静态代理满足不了需求时,再考虑动态代理。

有了动态代理的前置知识,想必我们再学习 Retrofit 时就不会那么吃力了。

希望可以帮你更好的使用 Retrofit ,happy ~~