ProgrammingJavaアーキテクト

プロキシとは何ですか?Javaにおけるプロキシの種類と、どのようにしてそれを用いてオブジェクトの動的な振る舞いを実現するか?

Hintsage AIアシスタントで面接を突破

答え。

問題の歴史:

プロキシオブジェクトとプロキシパターンは、Javaで置き換え用のオブジェクトを作成し、実際のオブジェクトへのアクセスを制御したり、その振る舞いを変更したり、クロスカット機能(セキュリティ、ロギング、メトリクス)を埋め込むために登場しました。Java 1.3以降、java.lang.reflectパッケージで標準のダイナミックプロキシのサポートが追加され、後にCGLIBやByteBuddyといった人気のあるライブラリが登場しました。

問題:

既存のクラスのロジックを直接変更することは、いつも可能であるとは限りません。しばしば、オブジェクトのソースコードを変更せずに(たとえば、ロギング、キャッシング、トランザクションなど)の振る舞いを透明に追加することが必要ですが、これは標準の継承手法では不可能です。

解決策:

プロキシオブジェクトは、静的に(手動のサブクラス化によって)または動的に(リフレクションやバイトコードメカニズムを通じて)実装できます。ダイナミックプロキシは、必要なインターフェースを実装するオブジェクトをその場で作成し、invoke()メソッド内で実際のオブジェクトへの呼び出しを委任することを可能にし、外部の振る舞いを埋め込む柔軟性を提供します。

コードの例:

import java.lang.reflect.*; interface Service { void doWork(); } class RealService implements Service { public void doWork() { System.out.println("Doing work!"); } } class LoggingInvocationHandler implements InvocationHandler { private final Object target; public LoggingInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Calling: " + method.getName()); return method.invoke(target, args); } } Service real = new RealService(); Service proxy = (Service) Proxy.newProxyInstance( real.getClass().getClassLoader(), new Class[] {Service.class}, new LoggingInvocationHandler(real)); proxy.doWork(); // ログに呼び出しが記録される

主な特徴:

  • 静的プロキシは、各関数に対して事前に記述されたコードを必要とする
  • ダイナミックプロキシは、実行時に任意のインターフェース用にその場で作成される
  • インターフェースのないクラスには、第三者ライブラリが必要(例:CGLIB)

トリック質問。

標準のJavaのダイナミックプロキシは、インターフェースを実装していないクラスで機能しますか?

いいえ。標準のJavaプロキシはインターフェースでのみ動作します。インターフェースを実装していないクラス(たとえば、通常のクラスをプロキシしたい場合)には、サードパーティの手段が必要です(例:CGLIB、ByteBuddy)。

プライベートメソッドを持つインターフェースのプロキシは作成できますか?

いいえ。Javaのインターフェースは、プロキシする必要のあるプライベートメソッドを含めることができません。ダイナミックプロキシはインターフェースのパブリックメソッドを実装します。Java 9以降、プライベートデフォルトメソッドが登場しましたが、これらはプロキシを通じてアクセスできません。

InvocationHandlerとMethodInterceptor(CGLIB)の違いは何ですか?

InvocationHandlerは、ダイナミックプロキシに使用されるJDKの標準インターフェースです。インターフェースのメソッドの呼び出しを受け取ります。MethodInterceptorは、CGLIBのインターフェースであり、動的継承を通じて任意のクラスのメソッド呼び出しをインターセプトすることを可能にします。適用性とレベルは異なります:JDKはインターフェースのみに、CGLIBは任意のクラスに対応しています。

よくある間違いやアンチパターン

  • 型の間違い:インターフェースを実装していないクラスに対してプロキシを使用しようとする
  • 暗黙の循環呼び出しによりStackOverflowを引き起こす
  • プロキシ化されていないクラスやfinalクラスのプロキシ化を試みる

実際の例

ネガティブケース

エンジニアがすべてのサービスのメソッドで手動でロギングのコードを繰り返している。新しいメソッドを追加するたびに、論理を重複して記述し、必要な呼び出しを追加し忘れることが多い。

長所:

  • 透明で、処理の流れが理解しやすい

短所:

  • 時間の浪費、多くの単調なコード、保守が難しい

ポジティブケース

プロジェクトでダイナミックプロキシを介してAOPが導入され、ロギングとトランザクション管理が中央集権的に実装されたラッパーによって実現されます。

長所:

  • どんなクロスカット機能でも迅速に導入・変更でき、重複がない

短所:

  • プロキシがどのように機能するかを理解する必要があり、デバッグにおいて潜在的な困難がある