likes
comments
collection
share

[设计模式]代理模式

作者站长头像
站长
· 阅读数 12

定义

代理模式的定义其实比较简单:代理模式给某对象提供一个代理对象,由代理对象来控制对原对象的引用。生活中比较常见的代理模式的应用比如:火车票代购、代办保险、UU 跑腿、武侠片中的替身、nginx 反向代理等等这种类似于中介的模式统统可以归于代理模式。“本人”和“代理人”都是对象,“代理人”可以代替“本人”去完成一些工作,甚至是出色完成(超期完成)某些工作,这里的“出色完成”就可以理解为对原工作的一种增强实现,这点有点类似于装饰器模式。感兴趣的读者不妨翻到前面装饰器模式一节中了解下装饰器模式。

[设计模式]代理模式 图1 - 不使用代理模式

[设计模式]代理模式 图2 - 使用代理模式

那么,为什么会存在代理模式呢?我们知道,存在即合理,很多情况下,客户类不想或不能直接引用委托对象,这时候使用代理类充当中介作用,这种情况下代理类和委托类实现相同的接口;另外,有时候我们会想增强委托类,这个时候使用代理类来完成也是再合适不过了,也是符合开闭原则(对拓展开放,对修改关闭)的。

组成角色

代理模式一般分为两种:静态代理和动态代理(jdk动态代理和cglib动态代理),这里我们先来看下静态代理,静态代理的通用类图如下 [设计模式]代理模式

代理模式也叫做委托模式,代理类一般包含被委托类的引用,下面我们来说下上面三个角色的定义:

  • 抽象主题角色(Subject):抽象主题角色往往是一个抽象类或接口,用来定义被委托类也就是真实的业务处理类和代理类的一些通用操作;
  • 具体的主题角色(RealSubject):该类实现 Subject,是真实被委托的类,也是具体业务逻辑的真正执行者;
  • 代理类或委托类(Proxy):该类同样实现 Subject,在客户类和本地之间充当中介作用,将客户端的业务操作委托给 RealSubject 执行,并在执行前后做一些预处理或者善后工作。有点类似于AOP,实际上AOP使用的也是代理模式,不过是动态代理。

使用实例

上面说了那么多,我们简单的举个静态代理模式的例子吧,我们以购票为例,定义一个购票接口 IBuyer,然后定义一个具体的实现类 Buyer,模式类图简单如下: [设计模式]代理模式

代码非常简单,我们先来看下 IBuyer 接口的代码:

public interface IBuyer {

    /**
     * 购票登录接口
     * @param username 用户名
     * @param password 密码
     */
    void login(String username, String password);

    /**
     * 模拟购票接口
     */
    void bugTicket();
}

然后是购票的具体实现类:

public class Buyer implements IBuyer{

    private String name; // 模拟当前购票人

    public Buyer(String name) {
        this.name = name;
    }

    @Override
    public void login(String username, String password) {
        System.out.println("用户:" + username + " 使用密码:" + password + " 已登录成功");
    }

    @Override
    public void bugTicket() {
        System.out.println(name + " 正在购票");
    }
}

最后是我们的测试类:

public class Main {

    public static void main(String[] args) {
        String name = "caiya";

        IBuyer buyer = new Buyer(name);
        // 登录
        buyer.login(name, "123456");
        // 开始购票
        buyer.bugTicket();
    }
}

测试输出如下:

用户:caiya 使用密码:123456 已登录成功
caiya 正在购票

可以看到,在不使用代理模式的情况下一切实现起来也并不复杂,运行结果也都 ok。可是现实生活却并不是这般完美,我们往往买票时并非自己手动操作,而是会经常性地去“刷票”,这就体现出黄牛的价值了,黄牛可以理解成我们的 Buyer 的代理类,使用代理类时大致的类图设计如下: [设计模式]代理模式

public class BuyerProxy implements IBuyer{

    private IBuyer buyer;

    public BuyerProxy(IBuyer buyer) {
        this.buyer = buyer;
    }

    @Override
    public void login(String username, String password) {
        this.buyer.login(username, password);
    }

    @Override
    public void bugTicket() {
        before();
        this.buyer.bugTicket();
        after();
    }

    private void before() {
        System.out.println("准备定时任务,开始刷票");
    }

    private void after() {
        System.out.println("刷票成功,短信通知客户");
    }

这里我们给购票接口加入了 before 预处理和 after 的善后处理工作,测试及输出如下:

// 使用代理类完成购票操作
String name = "caiya";
IBuyer buyer = new Buyer(name);
IBuyer proxyBuyer = new BuyerProxy(buyer);
proxyBuyer.login(name, "123456");
proxyBuyer.bugTicket();

具体输出如下:

用户:caiya 使用密码:123456 已登录成功
准备定时任务,开始刷票
caiya 正在购票
刷票成功,短信通知客户

可以看到,我们在正常购票的同时,前置和后置的增强功能也得到了实现,这就是代理模式的魅力,在不改变原有类代码的情况下实现功能增强。

动态代理

上面讲了一下静态代理的基础实例,接下来我们学习下什么是动态代理,在上面静态代理中我们使用手动创建的代理类来实现业务代理,倘若我们现在要服务多种类型的对象,那么是不是要为每个对象都要建立一份代理,这显然是不切实际的。另外静态代理中的接口一旦发生变化,那么意味着代理类也要进行重写,这在大型项目中是不允许的,针对以上两种情况,所以就产生了动态代理。

jdk 动态代理

jdk 的动态代理是通过反射技术来创建类的加载器并且创建类的实例,根据类执行方法并在方法执行前后进行前置或者后置通知等处理。使用到的就是 Proxy 类的静态方法 newProxyInstance,该方法会返回一个代理类对象:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

下面我们使用 jdk 的动态代理来实现下上面 BuyerProxy 代理类的功能,这里就直接贴代码来看吧:

public class Main {

    public static void main(String[] args) {
        String name = "caiya";
        IBuyer buyer = new Buyer(name);

        // 使用jdk 代理类完成 BuyerProxy 操作,重点是如何实现前置后置通知
        IBuyer proxy = (IBuyer) Proxy.newProxyInstance(buyer.getClass().getClassLoader(), buyer.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object obj;
                // 这里我们在 bugTicket 操作前后进行通知操作,来模拟 BuyerProxy
                if ("bugTicket".equals(method.getName())) {
                    System.out.println("准备定时任务,开始刷票");
                    obj = method.invoke(buyer, args);
                    System.out.println("刷票成功,短信通知客户");
                } else {
                    obj = method.invoke(buyer, args);
                }
                return obj;
            }
        });
        proxy.login(name, "123456");
        proxy.bugTicket();
    }
}

测试输出如下:

用户:caiya 使用密码:123456 已登录成功
准备定时任务,开始刷票
caiya 正在购票
刷票成功,短信通知客户

可以看到,使用 jdk 动态代理相比静态代理优势更加明显,免去了代理类的编写,由于使用了反射技术,往往 jdk 动态代理效率没有静态代理高。

jdk 的动态代理使用的静态方法第二个参数就是目标对象实现的接口类型,所以需要被代理的目标对象实现一个或多个接口,倘若目标对象没有实现的接口怎么办?这个时候就可以考虑 cglib 动态代理。

注:静态代理和 jdk 动态代理有一个共同点就是代理类和被代理的目标对象要实现同样的即可。

cglib 动态代理

我们来看个简单例子,对于如下购票类,是没有实现接口的比较直接的类,我们要对该类实现类似前置、后置操作就可以使用 cglib 动态代理,先来看下这个购票类:

public class Ticketer {

    private String name; // 模拟当前购票人

    public Ticketer(String name) {
        this.name = name;
    }

    public Ticketer() {
    }

    public void login(String username, String password) {
        System.out.println("用户:" + username + " 使用密码:" + password + " 已登录成功");
    }

    public void bugTicket() {
        System.out.println(name + " 正在购票");
    }
}

接下来主角登场,我们使用 cglib 动态代理来实现 BuyerProxy 的类似功能(注意:cglib 需要引入 cglib 的 jar 文件,因为 spring 包含 cglib 的功能,所以引入 spring-core 的 jar 包也可以):

public class Main {

    public static void main(String[] args) {
        String name = "caiya";
        Ticketer buyer = new Ticketer(name);

        // 工具类
        Enhancer enhancer = new Enhancer();
        // 设置代理对象的父类
        enhancer.setSuperclass(buyer.getClass());
        // 设置回调函数
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object obj;
                // 这里我们在 bugTicket 操作前后进行通知操作,来模拟 BuyerProxy
                if ("bugTicket".equals(method.getName())) {
                    System.out.println("准备定时任务,开始刷票");
                    obj = method.invoke(buyer, args);
                    System.out.println("刷票成功,短信通知客户");
                } else {
                    obj = method.invoke(buyer, objects);
                }
                return obj;
            }
        });
        // 创建子类(代理对象)
        Ticketer ticketerProxy = (Ticketer)enhancer.create();
        ticketerProxy.login(name, "123456");
        ticketerProxy.bugTicket();
    }
}

执行结果如下,可以看出和之前功能完全一致:

用户:caiya 使用密码:123456 已登录成功
准备定时任务,开始刷票
caiya 正在购票
刷票成功,短信通知客户

代理模式 Vs 装饰器模式

前面一节中我们还介绍了装饰器模式,两者几乎类图是一致的,区别在于应用侧重点不同,装饰器模式侧重于类的装饰,而代理模式侧重于请求代理,这是我的个人理解,有不同意见的也可以相互讨论。

总结

本小节我们介绍了代理模式的定义、应用等等,又列举了静态代理和动态代理以及相应的实例,想必大家已经知道代理模型的具体含义了,下面我们总结下本节的内容: [设计模式]代理模式