プロキシパターンは、元のクラスのコードを変更することなく、プロキシ・クラスを導入することで、元のクラスに機能を追加します。わかりやすく言うと、 メソッドのラッピングや メソッドの拡張が可能です。
プロキシパターンには、主に静的プロキシ、動的プロキシ、CGlib プロキシの3 つの形式があります。
プロキシパターンの分析
プロキシパターンを説明する簡単な例を示します。先生の講義の時間を記録するために、ビジネスコードでこのようなことができるとします:
public class TeachService {
 public void doTeach() {
 Long startTimestamp = System.currentTimeMillis(0);
 // 教えるビジネスロジックを省略する...
 System.out.println("Do teach.");
 Long endTimeStamp = System.currentTimeMillis();
 Long teachTime = endTimeStamp - startTimestamp;
 System.out.println("Time: " + teachTime);
 }
}
まず、タイミング関連のコードがビジネス・コードに入り込んでおり、ビジネス・コードと高度に結合しています。後でこの部分を削除したり、他の関数に変更する必要がある場合、変更にかかるコストは比較的大きくなります。第二に、単一責任の原則に違反します。タイミングに関連するコードはビジネスコードとは無関係であり、クラス内に置くべきではありません。
以下は、上記3種類の代理パターンの違いを見るために、それぞれの代理パターンを使ったプログラムの改善です。
静的プロキシ
アプリケーションが実行される前にプロキシ・クラスが既に存在している場合、このタイプのプロキシは静的プロキシと 呼ばれ、この場合、プロキシ・クラスは通常 Java コードで定義されます。この場合、プロキシ・クラスは通常 Java コードで定義されます。 静的プロキシを実装するには、インタフェースまたは親クラスを定義する必要があり、プロキシされるクラスはプロキシ・クラスと共に同じインタフェースまたは親クラスを実装します。
ここで、プロキシクラスTeachProxyは元のクラスTeachImplと同じインタフェースTechを実装しています。 TeachImplクラスは業務機能のみを担当します。プロキシクラスTeachProxyは、業務コードの実行前後に他のロジックコードをアタッチしたり、元のクラスを呼び出して業務コードをデリゲートして実行したりする役割を担っています。具体的なコードを以下に示します。
/**
 * Teach
 */
public interface Teach {
 void doTeach();
}
/**
 * TeachImpl
 */
public class TeachImpl implements Teach {
 @Override
 public void doTeach() {
 System.out.println("Do teach.");
 // 教えるビジネスロジックを省略する...
 }
}
/**
 * TeachProxy
 */
public class TeachProxy implements Teach {
 private Teach teach;
 public TeachProxy(Teach teach) {
 this.teach = teach;
 }
 @Override
 public void doTeach() {
 Long startTimestamp = System.currentTimeMillis(0);
 // ビジネス関数の実行を元のクラスに委譲する
 this.teach.doTeach();
 Long endTimeStamp = System.currentTimeMillis();
 Long teachTime = endTimeStamp - startTimestamp;
 System.out.println("Time: " + teachTime);
 }
}
// TeachProxy 使用例
Teach teachProxy = new TeachProxy(new TeachImpl());
teachProxy.doTeach();
静的エージェントの特徴
元のクラスの機能を変更しないという前提の下、プロキシ・クラスを通して元のクラスの機能を拡張することができ、同時にクライアントと元のクラスの間のデカップリングを実現することができます。
しかし、スタティックプロキシの限界は、実行前にプロキシオブジェクトを記述する必要があることです。 プロキシクラスと元のクラスは同じインタフェースを実装する必要があるため、プロキシクラスが多数存在することになり、インタフェースが高機能化すると、元のクラスとプロキシクラスの両方をメンテナンスする必要があるため、コードのメンテナンスコストが増大します。
動的プロキシ
静的プロキシの使用から生じる問題は、動的プロキシを使用することで解決できます。
ダイナミック・プロキシは、実行時に作成されるプロキシ・クラスです。この場合、プロキシ・クラスはJavaコードで定義されるのではなく、Javaコード内の「命令」に基づいて実行時に動的に生成され、その後、プロキシ・クラスは元のクラスを置き換えるためにシステムで使用されます。
プロキシオブジェクトの生成は、JDKのAPI(java.lang.reflectパッケージ)を使用して、動的にメモリ内のオブジェクトを作成するので、動的なプロキシは、また、JDKのプロキシと呼ばれるように、動的なプロキシ実装を使用する場合、プロキシクラスは、インターフェイスを実装する必要はありません。
JDKは動的プロキシを実装しており、java.lang.reflectパッケージのProxyクラスのメソッドを使用する必要があります:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
メソッド・パラメータの説明:
ClassLoader loader: 現在のターゲット・オブジェクトのクラス・ローダー。
Class<?>[] interfaces: ターゲットオブジェクトが実装するインターフェースのタイプ。
InvocationHandler h: ターゲットオブジェクトのメソッドが実行されたときにトリガーされるハンドラで、ターゲットオブジェクトの現在実行されているメソッドをパラメータとして渡します。
次の動的エージェントは、プログラムを改善するために使用され、具体的なコードは次のとおりです:
/**
 * Teach
 */
public interface Teach {
 void doTeach();
}
/**
 * TeachImpl
 */
public class TeachImpl implements Teach {
 @Override
 public void doTeach() {
 System.out.println("Do teach.");
 // 教えるビジネスロジックを省略する...
 }
}
/**
 * TeachProxy
 */
public class TeachProxy {
 private Object target;
 public TeachProxyFactory(Object target) {
 this.target = target;
 }
 public Object getInstance() {
 return Proxy.newProxyInstance(
 target.getClass().getClassLoader(),
 target.getClass().getInterfaces(),
 new InvocationHandler() {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 Long startTimestamp = System.currentTimeMillis(0);
 Object invoke = method.invoke(target, args);
 Long endTimeStamp = System.currentTimeMillis();
 Long teachTime = endTimeStamp - startTimestamp;
 System.out.println("Time: " + teachTime);
 return invoke;
 }
 }
 );
 }
}
// TeachProxy 使用例
Teach teachProxy = (Teach) new TeachProxy(new TeachImpl()).getInstance();
teachProxy.doTeach();
ダイナミック・エージェントの特徴
静的プロキシに対する動的プロキシの利点は、それぞれのプロキシ・クラスの関数を変更することなく、プロキシ・クラスの関数を簡単に統一できることです。ただし、動的プロキシのプロキシ・クラスのバイトコードは、生成時に元のクラスが実装しているインタフェースをパラメータとして実装する必要があります。元のクラスがインタフェースを実装せず、ビジネスメソッドを直接定義している場合、JDKのダイナミックプロキシは使用できません。
動的プロキシはインターフェースに基づいて実装され、クラスではなくインターフェースに対してのみプロキシできます。
CGlib
元のクラスがインターフェイスを定義しておらず、元のクラスのコードが開発者によって保守されていない場合、元のクラスを直接修正してインターフェイスを再定義する方法はありません。この場合、CGlib プロキシを使うことができます。
CGlib のプロキシパターンは、インターフェイスを実装することなく、 プロキシされるクラスを継承してプロキシのサブクラスを生成することに基づいています。必要なのは、プロキシされるクラスが非終端クラスであることだけです。(CGlib プロキシは ASM バイトコード技術に基づいています。
正確なコードを見てみましょう:
/**
 * Teach
 */
public class Teach {
 public void doTeach() {
 System.out.println("Do teach.");
 }
}
/**
 * TeachMethodInterceptor
 */
public class TeachMethodInterceptor implements MethodInterceptor {
 private Class<?> target;
 public TeachMethodInterceptor(Class<?> target) {
 this.target = target;
 }
 public Object getProxyInstance() {
 // プロキシされるクラス、つまり親クラスをプロキシクラスに指定する。
 Enhancer enhancer = new Enhancer();
 // プロキシクラスのすべてのメソッド呼び出しに対して、メソッドインターセプターコールバック参照を設定する,
 // さらに、CallBackが呼び出され、CallBackはインターセプトのためにintercept()メソッドを実装する必要がある。
 enhancer.setSuperclass(target);
 // ダイナミックプロキシクラスオブジェクトを取得し
 enhancer.setCallback(this);
 return enhancer.create();
 }
 @Override
 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
 Long startTimestamp = System.currentTimeMillis(0);
 Object result = proxy.invokeSuper(obj, args);
 Long endTimeStamp = System.currentTimeMillis();
 Long teachTime = endTimeStamp - startTimestamp;
 System.out.println("Time: " + teachTime);
 return result;
 }
}
// 使用例
TeachMethodInterceptor methodInterceptor = new TeachMethodInterceptor(Teach.class);
Teach proxyInstance = (Teach) methodInterceptor.getProxyInstance();
proxyInstance.doTeach();
MethodInterceptor.intercept() 上記のコードでは、プロキシされる元のオブジェクトは CGlib の Enhancer クラスによって指定され、プロキシオブジェクトは create() メソッドを呼び出すことで最終的に取得されます。このオブジェクトに対する最終的でないメソッド呼び出しはすべて、 メソッドパラメータの変更、ロギング機能の追加、セキュリティチェック機能など、 任意のロジックを追加できるメソッドに転送されます。 MethodProxy.invokeSuper() intercept() メソッドでは、メソッド・パラメータの変更、ロギング機能の追加、セキュリティ・チェック機能の追加など、任意のロジックを追加することができます。
CGlib エージェントの機能
CGLiBは動的プロキシを実装しており、CGLibの基礎となるASMバイトコード生成フレームワークでは、バイトコード技術を使ってプロキシクラスを生成するため、Javaのリフレクションを使うよりも効率的です。CGLibの原則は、プロキシされたクラスのサブクラスを動的に生成することであり、これは継承に基づいて実装されています。
Proxyパターンの適用シーン
エージェントパターンは、一般的にビジネスシステムで、監視、統計、権限制御、フロー制限、トランザクション、ログなどの非機能要件を開発するために使用されます。これらの追加関数とビジネス関数は切り離され、プロキシクラスで統一処理されるため、プログラマはビジネスサイドの開発だけに集中することができます。さらに、プロキシパターンは、RPCフレームワーク、キャッシュや他のアプリケーションシナリオでも使用することができます。
概要
プロキシパターンは主にメソッドの拡張に使用され、元の機能を変更せずに追加機能を拡張し、元の機能と追加機能を分離するという目的を達成します。





