结构型设计模式之代理模式

概述

所谓代理模式,实际上就是「将某一件事托管给第三方去做」,如我们买房子一般情况下并不是直接联系房主,而是通过中介去了解房子,中介做为我们买房子这件事的第三方。 我们不需要知道中介是如何联系客户的,以及其他的很多细节,我们只需要联系中介即可。这就叫「代理模式」

有一件很重要的事情,在学习设计模式的时候,我认为第一步应该知道它能做什么,然后再看自己应不应该学,最后才是学。否则为了学而学,是没有意义的。

那么代理模式能给我们带来什么呢?
1.隐藏具体功能实现细节
2.对原先的方法进行加强,不影响原先的使用者
3.对一个类的所有(或自定)方法进行批量加强

注意,这里的「加强」的含义是指在原先代码的基础上增加其他代码。

而代理模式又分为:

  • 静态代理模式
  • 动态代理模式

且随文章逐个学习,便明悟。

整体

静态代理

根据概述中所阐述,想必已经知道它的具体概念了。那么我们在代码中可以将这个模式进行抽象一下。它分为三大块,分别是:

  • 抽象主题(接口),定义事迹主题所需要的的方法
  • 真实主题(实现类),定义抽象主题中的具体业务,是最终被代理的对象
  • 代理类,也实现了抽象主题接口,在内部含有对真实主题的引用,它可以访问、控制、拓展真实主题的功能

当学习到这里,我的第一个想法就是——这不就是Spring中的AOP嘛?好吧,AOP实际上就是通过代理模式所实现的。

首先,我们现在联想一个「用户通过第三方购票APP购买火车票」的场景。它的抽象主题是什么?买票嘛,那么通过谁买票呢? 自然是车站买票啦。因此我们得知,一般情况下我们去车站买票还需要排队啊什么什么的,这是就出现了一个代理人——第三方购票APP。

好了,场景分析完毕下面直接开始代码。

购票接口

public interface SellTickets {
    void sell();
}

车站(真实主题)

public class TrainStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("成功购买一张火车票");
    }
}

第三方购物APP(代理类)

public class ProxyPoint implements SellTickets{

    private TrainStation trainStation = new TrainStation();

    /**
     * 通过「代理点」的封装,可以实现对真实主题的加强
     */
    @Override
    public void sell() {
        System.out.println("我是代理点,我收取了5%的费用");
        trainStation.sell();
        System.out.println("我是代理点,我抢到了票");

    }
}

最终客户端

写到这里你应该明白了,我们通过一个代理类(中间人)对「购票」这件事进行了一层代理。很简单吧?这就是静态代理模式,我们可以通过这种模式去隐藏、拓展代码中的细节。

    public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        proxyPoint.sell();
    }

动态代理

通过上述的代理模式,你应该已经明白它的主要作用了吧。不知道你有没有觉得这种方式是不是好麻烦呀,我们每增加一些代码,可能都需要我们去专门创建一个代理类。因此,就衍生了「动态代理」模式。它的好处在于所有增加的代码使用一个集中的方法进行管理。

而动态代理一共有两种实现方式,一种是JDK中自带的方式,它的原理是通过对应的接口去代理,而第二种是通过第三方工具库「cglib」去通过字节码技术创建子类代理。说到这里很绕吧,没事,我们只需要记住当要代理的「主题」是通过接口实现的,我们就可以直接通过JDK自带的方式,反之通过cglib库。

JDK动态代理模式

同样的,我们根据上述「静态代理」的段落所示,我们仍然需要「车站、第三方购票APP、买票接口」三个要素,这里就不再复制。

按照之前的思路,我们在动态代理形式中已经不需要重新创建代理类了,可以集中在一个方法之中。

如下所示,我们创建了一个名为「ProxyFactory」的代理工厂类,并在其中编写了「getProxyObject()」方法,很显然,我们通过这样一个方法能够获得一个「加强的买票方法」

public class ProxyFactory {

    private TrainStation trainStation = new TrainStation();

    public SellTickets getProxyObject() {
        return (SellTickets) Proxy.newProxyInstance(
                trainStation.getClass().getClassLoader(),
                trainStation.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代收点收取了一定的服务费用");
                        return method.invoke(trainStation, args);
                    }
                }
        );
    }
}

具体是怎么实现的呢?主要代码在于「Proxy.newProxyInstance()」方法,它是JDK中提供创建动态代理的方法,一共拥有三个参数,即:

  • 目标接口的类加载器
  • 目标接口的类字节码
  • 具体的实现方式

很显然,前两者都是要的接口类对应的信息,那么我们大胆的想象,「Proxy.newProxyInstance()」方法便是通过这两者进行构建的接口代理。而第三个参数呢,便是我们具体要执行(加强)那些代码,在其中编写。

此处的重中之重便是「InvocationHandler」中的「invoke」方法,它其中携带了三个参数,分别是:

  • Object proxy 代理对象
  • Method method 代理对象所调用的方法
  • Object[] args 代理对象调用方法所传递的参数

当用户使用我们所构造的动态代理类的时候,会直接执行「InvocationHandler」中的「invoke」方法。我们可以直接在这个方法之中对其进行操作。

因此,我们在代码中可以直接编写「要加强的代码」和调用原先的代码。那么,我们该如何调用原先的方法呢?我们可以直接通过第二个参数(Method)的「invoke」方法去实现。需要将被代理的对象为参数(车站类)。这里要注意歧义,Method中的「invoke」方法与当前的「InvocationHandler」中的「invoke」方法毫无相关。 前者的作用是调用被代理对象(车站)与当前「method」所对应的方法。

之后,我们将对应方法的返回值返回出去,便完成了动态代理的操作。

public class Client {
    public static void main(String[] args) {
        SellTickets proxyObject = new ProxyFactory().getProxyObject();
        proxyObject.sell();
    }
}

cglib

首先,我们需要通过maven引入对应库的坐标。

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>

当我们将包导入成功之后,便可以直接进行操作。通过前面所讲述,cglib主要通过字节码技术对目标类子类代理,并且不需要接口。

首先,我们需要创建一个「Enhancer」类型的对象。它便是创建动态代理的构造器,我们需要告诉它,我们要对那个类进行加强,因此使用「setSuperclass」方法。

 Enhancer enhancer = new Enhancer();
 enhancer.setSuperclass(TrainStation.class);

那么,我们在哪里加强呢?这个时候我们就需要去创建一个回调函数了,即在其中编写加强代码。

 class Proxy implements MethodInterceptor{
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(11);
        method.invoke(trainStation,args);
        return null;
    }
}

需要实现「MethodInterceptor」接口,并实现「intercept」方法,这个方法与上述JDK动态代理相似,便不再撰述。
其后通过以下代码完成创建。

  enhancer.setCallback(new Proxy());
   TrainStation t =  (TrainStation) enhancer.create();

便完成。

总结

今天详细学习了设计模式中的代理模式,但是存在一个问题——知道如何使用JDK和cglib为我们提供的方法,但并不知道具体是如何实现的,因此导致本篇博文中可能产生很多歧义。后续应不断学习补充,以待将来对本篇博文查漏(错)补缺。