オタクタイム-デザインパターンの美しさ:RPC、キャッシング、モニタリング、その他のシナリオでのプロキシモードの適用

シングルトンモードは、グローバルに一意のオブジェクトを作成するために使用されます。ファクトリパターンは、異なるが関連するタイプのオブジェクト(同じ親クラスまたはインターフェイスを継承するサブクラスのグループ)を作成するために使用され、指定されたパラメータによって、作成するオブジェクトのタイプが決まります。ビルダーモードは、複雑なオブジェクトを作成するために使用されます。さまざまなオプションのパラメーターを設定することで、さまざまなオブジェクトを「カスタマイズ」して作成できます。プロトタイプモードは、比較的大きなコストでオブジェクトを作成することを目的としており、作成時間を節約するために既存のオブジェクトをコピーしてオブジェクトを作成します。

今日から、別のタイプの設計パターン、つまり構造パターンの学習を開始します。構造モデルは、主にいくつかのクラスまたはオブジェクトの古典的な構造を要約します。これらの古典的な構造は、特定のアプリケーションシナリオの問題を解決できます。構造モードには、エージェントモード、ブリッジモード、デコレータモード、アダプタモード、ファサードモード、組み合わせモード、およびフライウェイトモードが含まれます。今日はエージェンシーモデルについてお話します。また、実際の開発でよく使われるデザインパターンでもあります。

プロキシモードの原理の分析

エージェントモード(プロキシデザインパターン)の原則とコードの実装を習得するのは難しくありません。元のクラス(またはプロキシクラスと呼ばれる)のコードを変更せずにプロキシクラスを導入することにより、元のクラスに関数を追加します。この一節を簡単な例で説明しましょう。

MetricsCollectorクラスは、アクセス時間や処理時間など、インターフェイスから要求された生データを収集するために使用されます。ビジネスシステムでは、次のようにMetricsCollectorクラスを使用します。


public class UserController {
    
    
  //...省略其他属性和方法...
  private MetricsCollector metricsCollector; // 依赖注入

  public UserVo login(String telephone, String password) {
    
    
    long startTimestamp = System.currentTimeMillis();

    // ... 省略login逻辑...

    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);

    //...返回UserVo数据...
  }

  public UserVo register(String telephone, String password) {
    
    
    long startTimestamp = System.currentTimeMillis();

    // ... 省略register逻辑...

    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);

    //...返回UserVo数据...
  }
}

明らかに、上記の記述には2つの問題があります。まず、パフォーマンスカウンターフレームワークコードがビジネスコードに侵入し、ビジネスコードと高度に結合されています。このフレームワークを将来交換する必要がある場合、交換コストは比較的大きくなります。次に、インターフェイスリクエストを収集するためのコードは、ビジネスコードとは関係がないため、クラスに配置しないでください。ビジネスクラスにとっては、より単一の責任を持ち、ビジネス処理のみに焦点を当てることが最善です。

フレームワークコードとビジネスコードを分離するには、プロキシモデルが便利です。プロキシクラスUserControllerProxyと元のクラスUserControllerは、同じインターフェイスIUserControllerを実装します。UserControllerクラスは、ビジネス機能のみを担当します。プロキシクラスUserControllerProxyは、ビジネスコードの実行の前後に他のロジックコードをアタッチする役割を果たし、委任によって元のクラスを呼び出してビジネスコードを実行します。具体的なコードの実装は次のとおりです。


public interface IUserController {
    
    
  UserVo login(String telephone, String password);
  UserVo register(String telephone, String password);
}

public class UserController implements IUserController {
    
    
  //...省略其他属性和方法...

  @Override
  public UserVo login(String telephone, String password) {
    
    
    //...省略login逻辑...
    //...返回UserVo数据...
  }

  @Override
  public UserVo register(String telephone, String password) {
    
    
    //...省略register逻辑...
    //...返回UserVo数据...
  }
}

public class UserControllerProxy implements IUserController {
    
    
  private MetricsCollector metricsCollector;
  private UserController userController;

  public UserControllerProxy(UserController userController) {
    
    
    this.userController = userController;
    this.metricsCollector = new MetricsCollector();
  }

  @Override
  public UserVo login(String telephone, String password) {
    
    
    long startTimestamp = System.currentTimeMillis();

    // 委托
    UserVo userVo = userController.login(telephone, password);

    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);

    return userVo;
  }

  @Override
  public UserVo register(String telephone, String password) {
    
    
    long startTimestamp = System.currentTimeMillis();

    UserVo userVo = userController.register(telephone, password);

    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);

    return userVo;
  }
}

//UserControllerProxy使用举例
//因为原始类和代理类实现相同的接口,是基于接口而非实现编程
//将UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码
IUserController userController = new UserControllerProxy(new UserController());

元のクラスオブジェクトをプロキシクラスオブジェクトに置き換える場合は、実装プログラミングではなくインターフェイスに基づく設計アイデアを参照して、コードの変更をできるだけ少なくするために、現在のプロキシモードのコード実装では、プロキシクラスと元のクラスが同じインターフェイスを実装する必要があります。 。ただし、元のクラスでインターフェイスが定義されておらず、元のクラスコードが開発および保守されていない場合(たとえば、サードパーティのクラスライブラリからのものである場合)、元のクラスを直接変更してそのインターフェイスを再定義することはできません。この場合、プロキシモデルをどのように実装しますか?

この種の外部クラスの拡張には、通常、継承を使用します。これも例外ではありません。プロキシクラスに元のクラスを継承させてから、追加の機能を拡張します。原理は非常に単純で、あまり説明する必要はありません。コードを見れば直接理解できます。具体的なコードは次のとおりです。


public class UserControllerProxy extends UserController {
    
    
  private MetricsCollector metricsCollector;

  public UserControllerProxy() {
    
    
    this.metricsCollector = new MetricsCollector();
  }

  public UserVo login(String telephone, String password) {
    
    
    long startTimestamp = System.currentTimeMillis();

    UserVo userVo = super.login(telephone, password);

    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);

    return userVo;
  }

  public UserVo register(String telephone, String password) {
    
    
    long startTimestamp = System.currentTimeMillis();

    UserVo userVo = super.register(telephone, password);

    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);

    return userVo;
  }
}
//UserControllerProxy使用举例
UserController userController = new UserControllerProxy();

動的プロキシの原理分析

ただし、コードの実装にはまだいくつかの問題があります。一方では、プロキシクラスの元のクラスのすべてのメソッドを再実装し、各メソッドに同様のコードロジックをアタッチする必要があります。一方、追加する関数を持つクラスが複数ある場合は、クラスごとにプロキシクラスを作成する必要があります。

関数を追加する元のクラスが50ある場合、対応する50のプロキシクラスを作成する必要があります。これにより、プロジェクト内のクラスの数が指数関数的に増加し、コードメンテナンスのコストが増加します。さらに、各プロキシクラスのコードは、テンプレートスタイルの「複製」コードに少し似ているため、不要な開発コストも増加します。この問題を解決する方法は?

動的プロキシを使用して、この問題を解決できます。いわゆる動的プロキシ(動的プロキシ)とは、元のクラスごとに事前にプロキシクラスを作成するのではなく、実行時に元のクラスに対応するプロキシクラスを動的に作成し、元のクラスをシステム内のプロキシクラスに置き換えることを意味します。動的プロキシを実装する方法は?

Java言語に精通している場合、動的プロキシの実装は非常に簡単です。Java言語自体がすでに動的プロキシ構文を提供しているため(実際、基盤となる動的プロキシはJavaのリフレクション構文に依存しています)。Javaの動的プロキシを使用してこの機能を今すぐ実現する方法を見てみましょう。具体的なコードを以下に示します。その中で、MetricsCollectorProxyは、動的プロキシクラスとして、インターフェイス要求情報を収集する必要があるクラスごとにプロキシクラスを動的に作成します。


public class MetricsCollectorProxy {
    
    
  private MetricsCollector metricsCollector;

  public MetricsCollectorProxy() {
    
    
    this.metricsCollector = new MetricsCollector();
  }

  public Object createProxy(Object proxiedObject) {
    
    
    Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
    DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
    return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
  }

  private class DynamicProxyHandler implements InvocationHandler {
    
    
    private Object proxiedObject;

    public DynamicProxyHandler(Object proxiedObject) {
    
    
      this.proxiedObject = proxiedObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
      long startTimestamp = System.currentTimeMillis();
      Object result = method.invoke(proxiedObject, args);
      long endTimeStamp = System.currentTimeMillis();
      long responseTime = endTimeStamp - startTimestamp;
      String apiName = proxiedObject.getClass().getName() + ":" + method.getName();
      RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTimestamp);
      metricsCollector.recordRequest(requestInfo);
      return result;
    }
  }
}

//MetricsCollectorProxy使用举例
MetricsCollectorProxy proxy = new MetricsCollectorProxy();
IUserController userController = (IUserController) proxy.createProxy(new UserController());

実際、Spring AOPの基本的な実装原則は、動的プロキシに基づいています。ユーザーは、エージェントとして作成する必要のあるクラスを構成し、元のクラスのビジネスコードを実行する前後に実行する追加機能を定義します。Springは、これらのクラスの動的プロキシオブジェクトを作成し、JVM内の元のクラスオブジェクトを置き換えます。コードで最初に実行された元のクラスのメソッドは、プロキシクラスを実行するメソッドに置き換えられます。これにより、元のクラスに関数を追加することができます。

おすすめ

転載: blog.csdn.net/zhujiangtaotaise/article/details/110470606