# 代理模式

# 定义代理模式

  • 代理模式:为另一个对象提供一个替身,以让别人通过替身来控制对原本对象的访问;
  • 一般来讲,被代理的对象会是远程的对象,创建开销比较大的对象,或者需要安全控制的对象;

2020-2-22-23-55-32.png

  • Subject 为 RealSubject 和 Proxy 提供了接口;
  • 通过对统一接口的实现,Proxy 在 RealSubject 出现的地方能够替代它;
  • Proxy 代理并其他对象对 RealSubject 的访问;
  • Proxy 持有对 RealSubject 的引用,有时还会负责对他的创造和销毁;
  • 所有客户想要与 RealSubject 交互必须通过 Proxy;

# 虚拟代理

  • 虚拟代理作为创建开销比较大的对象的替身;
  • 通常在使用虚拟代理时,我们会在真正需要 “被代理对象” 时才创建被代理对象;
  • 在此之前,我们就用虚拟代理对象,来代替真正想要的对象;
  • 等真正需要的对象被创建好后,客户的请求还会通过代理被委托到目标对象上;

# 加载 CD 封面

  • 假如我想在一个音乐 APP 中加载 CD 封面;
  • 因为网络请求会有一个等待时间,在真正的封面被加载来之前,我们需要一个虚拟代理,来先替代一下;
  • 假设我们用 Swing 的 Icon 组件去显示封面;
  • 再封面加载好之前,先用虚拟代理用 Icon 组件显示 “请稍等,封面加载中。。。”

2020-2-23-0-4-0.png

2020-2-23-0-8-20.png

# 保护代理

  • 保护代理,是一种根据访问权限,决定客户是否被允许访问对象的代理
  • 🌰 例如:比如有一个对象,通过保护代理,雇员只可以访问其中是几个方法,而经理就可以访问全部的方法;
  • 🌰 保护代理就像是一个明星的经纪人,它会拦截所有外面打进来的电话。然后做一个筛选,只把有用的电话转接给明星;

# Java 动态代理

  • 我们可以利用 Java 的动态代理去实现保护代理;

2020-2-23-10-47-55.png

  • 动态代理,利用 Java 的反射技术(Java Reflection),在运行时创建一个动态代理类,实现一个或多个接口;
  • 注意:动态代理的是接口,不是类,更不是抽象类;
  • 于此同时,我们还要提供一个实现类 InvocationHandler 接口的实现类。客户对于 Proxy 上任何方法的调用都会被传入此类;
  • 再通过 InvocationHandler 实现类来控制对 RealSubject 的对应方法的访问;
  • 利用 Java 的 Proxy 类,调用 Proxy.newProxyInstance() 方法就可以创建动态代理;
  • Proxy.newProxyInstance() 方法有三个参数:
    • 代理要实现的接口的 “类加载器”;
    • 代理要实现的接口;
    • InvocationHandler 接口实现类;

# 现实案例

  • 现在我们有一个相亲 App;
  • 里面每个人都可以给其他用户打分(喜欢 or 不喜欢);
  • 每个用户的个人信息,自己都可以改。但是自己的分数不可以改;
  • 其他用户不可以改别人的个人信息。但是可以给别人打分;

下面 👇 是用户类的接口 PersonBean:

2020-2-23-14-23-44.png

  • 根据需求我们知道,用户不能给自己评分,也不能改变别人的个人信息;用户只能给别人打分,并修改自己的信息;
  • 可以使用保护代理来实现:
    • 首先创建两个 InvocationHandler:
      • OwnerInvocationHandler 用来处理用户对自己 PersonBean 对象的请求;
      • NonOwnerInvocationHandler 处理用户对别人 PersonBean 对象的请求;
    • 然后我们根据 PersonBean 接口创建一个动态代理;

首先创建 InvocationHandler

  • 当代理的方法被调用时,代理就会把这个调用转发给 InvocationHandler;
  • InvocationHandler 接口中只有一个 invoke() 方法。不管代理被调用的是什么方法,最后 Handler 处理器被调用的一定是 invoke 方法;
  • 之后 invoke 方法内部会去判断如何处理这个请求,可能会转发给 RealSubject 对象,也可能做其他的处理;
// InvocationHandler 是 java.lang.reflect 包的一部分
import java.lang.reflect.*;

public class NonOwnerInvocationHandler implements InvocationHandler {
    //把RealSubject要实例进来
    PersonBean person;

    // 实例化时,保存对 RealSubject 的引用
    public NonOwnerInvocationHandler(PersonBean person) {
        this.person = person;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 用户可以访问其他用户的信息
            // 如果方法名是 get 开头,就允许调用
            if (method.getName().startsWith("get")) {
                return method.invoke(person, args);
            // 用户也可以给其他用户评分
            } else if (method.getName().equals("setHotOrNotRating")) {
                return method.invoke(person, args);
            // 用户不可以改别人的信息,返回报错
            } else if (method.getName().startsWith("set")) {
                throw new IllegalAccessException();
            }
        }catch (InvocationTargetException e){
            e.printStackTrace();
        }

        //调用其他方法直接不理
        return null;
    }
}

创建 Proxy 类,并实例化 Proxy 对象

  • 现在我们要根据 PersonBean 接口,去创建一个动态代理;
  • 它会把客户对它的方法调用,转发给 👆 上面的两个 InvocationHandler;
  • 需要分别创建两个方法用来获取 Proxy 类。一个针对 NonOwner,另一个针对 Owner;
  • 使用 Proxy.newProxyInstance 来创建动态代理,分别接受三个参数:
    • 目标接口的类加载器;
    • 目标接口;
    • 调用处理器 InvocationHandler;
import java.lang.reflect.Proxy;

// 这个类是客户的类;
// 客户在里面通过 getOwnerProxy 和 getNonOwnerProxy 方法获取动态代理;
public class MatchMakingTestDrive {

    public PersonBean getOwnerProxy(PersonBean person) {
        // Proxy 代理是对 PersonBean 接口的实现;
        // 所以这里可以进行强制转型;
        return (PersonBean) Proxy.newProxyInstance(
                person.getClass().getClassLoader(),
                person.getClass().getInterfaces(),
                new OwnerInvocationHandler(person));
    }

    public PersonBean getNonOwnerProxy(PersonBean person) {
        return (PersonBean) Proxy.newProxyInstance(
                person.getClass().getClassLoader(),
                person.getClass().getInterfaces(),
                new NonOwnerInvocationHandler(person));
    }
}

在测试类里面去检测功能是否实现把

2020-2-23-14-53-22.png

上次更新: 8/22/2020, 8:14:09 AM