设计模式:代理模式

113 阅读4分钟

代理模式的定义

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

比如我们去租房子:租客就是访问对象 ,房东就是目标对象。我们直接找房东就是未使用代理模式。但我们委托中介去租房子,那中介就是代理

image-20211226194437208

优点与缺点

  • 优点
    • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
    • 代理对象可以扩展目标对象的功能(增强);
    • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性;
  • 缺点
    • 代理模式会造成系统设计中类的数量增加;
    • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
    • 增加了系统的复杂度;

以上阐述的缺点可以通过动态代理解决!!!

应用场景

  • 统计每个 api 的请求耗时
  • 统一的日志输出
  • 校验被调用的 api 是否已经登录和权限鉴定
  • Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程

结构与实现

结构

代理模式的主要角色如下:

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
  4. 客户(Client):访问代理对象。

在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。

根据代理的创建时期,代理模式分为静态代理和动态代理。

  • 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
  • 动态:在程序运行时,运用反射机制动态创建而成。

实现

静态代理

/**
 * client 客户端
 *
 * @author Strive
 */
public class StaticProxyClient {
  public static void main(String[] args) {
    Proxy proxy = new Proxy();
    proxy.rentHouse();
  }
}

/** 抽象主题 */
interface Subject {
  void rentHouse();
}

/** 真实主题 */
class RealSubject implements Subject {

  @Override
  public void rentHouse() {
    System.out.println("房东出租房子");
  }
}

/** 代理 */
class Proxy implements Subject {

  private RealSubject realSubject;

  @Override
  public void rentHouse() {
    if (realSubject == null) {
      realSubject = new RealSubject();
    }

    before();
    realSubject.rentHouse();
    after();
  }

  private void before() {
    System.out.println("中介带我看房子");
  }

  private void after() {
    System.out.println("中介带我办理租房合同");
  }
}

程序运行结果:

中介带我看房子
房东出租房子
中介带我办理租房合同

动态代理

  • InvocationHandler: 是通过一个代理实例零调用处理程序实现的接口。每个代理实例都有一个相关的调用处理程序。当一个方法是在一个代理实例调用,调用的方法进行编码并派遣其调用处理程序的invoke方法。
  • Proxy: 创建动态代理类的实例提供了静态方法,也是所有动态代理类的父类的方法创建。
/**
 * 动态代理
 *
 * @author Strive
 */
public class DynamicProxy {
  public static void main(String[] args) {
    // 真实主题 房东
    HostOwner hostOwner = new HostOwner();

    // 创建处理程序
    ProxyInvocationHandler pih = new ProxyInvocationHandler();
    // 通过调用代理处理程序来处理我们要调用的接口对象
    pih.setTarget(hostOwner);
    // 生成代理
    Rent proxy = (Rent) pih.getProxy();
    proxy.rentHouse();
  }
}

/** 代理处理程序:自动生成代理类 */
class ProxyInvocationHandler implements InvocationHandler {

  /** 被代理的接口 */
  private Object target;

  /**
   * 生成代理类
   *
   * @return 代理类
   */
  public Object getProxy() {
    return Proxy.newProxyInstance(
        this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
  }

  /** 处理代理实例,并返回结果 */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    before();
    Object invoke = method.invoke(target, args);
    after();
    return invoke;
  }

  public void setTarget(Object target) {
    this.target = target;
  }

  private void before() {
    System.out.println("中介带我看房子");
  }

  private void after() {
    System.out.println("中介带我办理租房合同");
  }
}

/** 抽象主题: 租房 */
interface Rent {
  void rentHouse();
}

/** 真实主题: 房东 */
class HostOwner implements Rent {

  @Override
  public void rentHouse() {
    System.out.println("房东出租房子");
  }
}

程序运行结果:

中介带我看房子
房东出租房子
中介带我办理租房合同

动态代理具体步骤:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。