静态代理和动态代理

代理模式(Proxy Pattern)

代理模式是程序设计中的一种设计模式, 属于结构型模式.

为其他对象提供一种代理以控制对这个对象的访问, 换句话说, 就是实现代理的类代表他所代理的另一个类的功能.

组成:

  • 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
  • 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

静态代理和动态代理

优点:

  • 职责清晰
    • 真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
  • 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
  • 高扩展性
    • 可以在目标对象实现的基础上,增强额外的功能操作,扩展目标对象的功能.

遵守开闭原则, 不修改别人已经写好的代码或者方法, 通过代理的方式来扩展该方法.

代理模式分为静态代理、动态代理

  • 静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
  • 动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

1. 静态代理

很多人的朋友圈中会有微商卖鞋, 他们从厂家拿货,然后在朋友圈中宣传,然后卖给熟人。

首先需要一个接口, 通用的接口是代理模式实现的基础.

接口命名为 SellShoes , 功能为卖鞋, 参数是数量, 返回总价

public interface SellShoes {
    int sell(int number);
}

接着还需要一个实现类, 一个名为 AdidasFactory 的类实现了这个接口, 表示微商从源头厂家拿货, 他实现了SellShoes接口

public class AdidasFactory implements SellShoes {
    //一双鞋150
    @Override
    public int sell(int number) {
        System.out.println("[代理]从[阿迪]批发了"+number+"双鞋, 花了"+150*number+"元");
        return 150*number;
    }
}

下面编写代理类 AdidasStaticProxy

public class AdidasStaticProxy implements SellShoes {

    private AdidasFactory adidasFactory;

    public AdidasStaticProxy(AdidasFactory adidasFactory) {
        this.adidasFactory = adidasFactory;
    }

    @Override
    public int sell(int number) {

        int sell = adidasFactory.sell(number);

        System.out.println("[买家]从[代理]购买了"+number+"双鞋, 花了"+8*sell+"元");
        return 8*sell;
    }
}

AdidasStaticProxy就是代理对象, 它有一个sell方法, 但是调用的是AdidasFactory的sell方法, 并对返回的值进行处理.

现在我们编写测试代码

新建一个Customer类代表顾客

public class Customer {
    public static void main(String[] args) {
        AdidasFactory adidasFactory = new AdidasFactory();
        AdidasStaticProxy adidasStaticProxy = new AdidasStaticProxy(adidasFactory);

        adidasStaticProxy.sell(5);
    }
}

测试结果:
静态代理和动态代理

现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。

静态代理的优点

  • 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情
  • 公共的业务由代理来完成,实现了业务的分工
  • 公共业务发生扩展时变得更加集中和方便

静态代理的缺点

  • 需要代理的类如果很多, 则需要为每一个类编写代理类, 工作量大, 开发效率低

2. 动态代理

上一节代码中 AdidasStaticProxy 类是代理,我们需要手动编写代码让 AdidasStaticProxy 实现 SellShoes 接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 SellShoes 接口的代理,而不需要去定义 AdidasStaticProxy 这个类。

假设微商小王拿到了Nike和Adidas两个厂家的代理. 我们进行代码的模拟。

直接借用上一节的接口SellShoes

另外编写一个工厂NikeFactory 实现上述接口

public class NikeFactory implements SellShoes {
    //一双鞋100
    @Override
    public int sell(int number) {
        System.out.println("[代理]从[耐克]批发了"+number+"双鞋, 花了"+100*number+"元");
        return 100*number;
    }
}

接下来需要编写小王, 这个动态代理人DynamicProxy

public class DynamicProxy implements InvocationHandler {
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.print("微商小王");
        String name = target.getClass().getName();
        log(name);

        return method.invoke(target, args);
    }

    private void log(String message){
        System.out.println("贩卖["+message+"]厂的鞋");
    }
}

然后, 顾客可以买鞋了Customer

public class Customer {
    public static void main(String[] args) {
        NikeFactory nikeFactory = new NikeFactory();
        AdidasFactory adidasFactory = new AdidasFactory();

        DynamicProxy dynamicProxy = new DynamicProxy();
        dynamicProxy.setTarget(adidasFactory);
        SellShoes proxy = (SellShoes) Proxy.newProxyInstance(adidasFactory.getClass().getClassLoader(), adidasFactory.getClass().getInterfaces(), dynamicProxy);
        proxy.sell(10);

        dynamicProxy.setTarget(nikeFactory);
        SellShoes proxy1 = (SellShoes) Proxy.newProxyInstance(nikeFactory.getClass().getClassLoader(), nikeFactory.getClass().getInterfaces(),dynamicProxy);
        proxy1.sell(50);
    }
}

输出结果:
静态代理和动态代理

我们并没有像静态代理那样为 SellShoes 接口实现一个代理类,但最终它仍然实现了相同的功能, 并且不同厂家都可以通过小王这一个代理来实现

小王不仅仅可以卖鞋子, 甚至可以卖衣服, 先编写一个卖衣服的接口SellClothes

public interface SellClothes {
    int sell(int number);
}

创建品牌工厂来实现接口UNIQLOFactory

public class UNIQLOFactory implements SellClothes{
    @Override
    public int sell(int number) {
        System.out.println("[代理]从[优衣库]批发了"+number+"件衣服, 花了"+50*number+"元");
        return 50*number;
    }
}

再次测试

public class Customer {
    public static void main(String[] args) {
        NikeFactory nikeFactory = new NikeFactory();
        AdidasFactory adidasFactory = new AdidasFactory();
        UNIQLOFactory uniqloFactory = new UNIQLOFactory();

        DynamicProxy dynamicProxy = new DynamicProxy();
        dynamicProxy.setTarget(adidasFactory);
        SellShoes proxy = (SellShoes) Proxy.newProxyInstance(adidasFactory.getClass().getClassLoader(), adidasFactory.getClass().getInterfaces(), dynamicProxy);
        proxy.sell(10);

        dynamicProxy.setTarget(nikeFactory);
        SellShoes proxy1 = (SellShoes) Proxy.newProxyInstance(nikeFactory.getClass().getClassLoader(), nikeFactory.getClass().getInterfaces(),dynamicProxy);
        proxy1.sell(50);

        dynamicProxy.setTarget(uniqloFactory);
        SellClothes proxy2 = (SellClothes) Proxy.newProxyInstance(uniqloFactory.getClass().getClassLoader(), uniqloFactory.getClass().getInterfaces(),dynamicProxy);
        proxy2.sell(50);
    }
}

测试结果
静态代理和动态代理

可见, 我们仅仅通过小王这一个代理就可以代理不同品牌(同一接口的不同实现类), 甚至贩卖不同类型的物品(不同接口)

3. Proxy 和 InvocationHandler

3.1 Proxy

Proxy类就是用来创建一个真实对象的真实代理对象,提供了很多方法,最常用的就是newProxyInstance方法

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

方法的作用就是创建一个代理类对象,总共有三个参数,含义如下:

  • loader:一般指的是真实对象的ClassLoader对象
  • interfaces:一般指的是真实对象所实现的接口.
  • h: 指的就是实现了InvocationHandler接口的代理类调用处理类,表示的是当动态代理对象调用方法时会关联到哪个实现了InvocationHandler接口的对象上,并进行转发到invoke()方法中

静态代理和动态代理

3.2 InvocationHandler

InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。

  • proxy 代理对象
  • method 代理对象调用的方法
  • args 调用的方法中的参数

静态代理和动态代理

因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。

4. 实际场景应用

校验用户权限,每一个菜单请求,都要判断一下请求的用户是否有该菜单权限。菜单多了,代码冗余,且容易遗漏。
通过动态代理就可以实现为:每一个用户,每一个菜单的请求,都经过代理(proxy),由他判断是否有权限,调用者只需要调用,实现自己的逻辑,不关心权限问题。

上一篇:租房不入坑不进坑,Python爬取链家二手房的数据,提前了解租房信息


下一篇:C++排序算法