プロキシパターンは、プロキシオブジェクトを導入することで制御対象のオブジェクトへのアクセスを可能にする一般的に使用される設計パターンです。Java では、プロキシ パターンが広く使用されており、権限チェック、キャッシュ、ロギングなどの追加機能を提供でき、ターゲット オブジェクトを変更せずに拡張できます。
1. プロキシモードの定義
プロキシパターンとは、プロキシオブジェクトを介して対象オブジェクトへのアクセスを制御し、対象オブジェクトを変更せずに機能の追加やアクセス制御を行うことを指します。プロキシ オブジェクトとターゲット オブジェクトは同じインターフェイスを実装しているため、クライアントはプロキシ オブジェクトを介してターゲット オブジェクトに間接的にアクセスできます。
プロキシ モードは構造設計モードに属し、システムにプロキシ オブジェクトを導入します。これにより、ターゲット オブジェクトへのクライアントの直接アクセスが置き換えられ、ターゲット オブジェクトに基づいて追加機能やアクセス制御を追加できます。
2. プロキシモードの原理
プロキシ パターンの中心的な考え方は、ターゲット オブジェクトへのアクセスを制御するためにプロキシ オブジェクトを導入することです。プロキシ オブジェクトとターゲット オブジェクトは同じインターフェイスを実装しているため、クライアントはプロキシ オブジェクトを介してターゲット オブジェクトに間接的にアクセスできます。プロキシ オブジェクトは、クライアントのリクエストを処理し、必要に応じてリクエストをターゲット オブジェクトに転送する責任を負います。このプロセス中に、プロキシ オブジェクトは、アクセス許可のチェック、キャッシュ、ロギングなどの追加のロジックを追加できます。
プロキシ モードの主な役割は次のとおりです。
- 抽象サブジェクト (Subject): プロキシ オブジェクトとターゲット オブジェクトの共通インターフェイス (通常は Java のインターフェイスまたは抽象クラス) を定義します。
- ターゲット オブジェクト (RealSubject): プロキシ オブジェクトによって表される実際のオブジェクトを定義し、ビジネス ロジックの特定の実行者です。
- プロキシ オブジェクト (Proxy): ターゲット オブジェクトへの参照を保持し、ターゲット オブジェクトと同じインターフェイスを実装し、メソッド呼び出しの前後に追加の操作を実行します。
プロキシ モードのワークフローは次のとおりです。
- クライアントはプロキシ オブジェクトにリクエストを送信します。
- プロキシ オブジェクトはリクエストを受信した後、リクエストがターゲット オブジェクトに転送される前または後に追加のロジックを実行できます。
- プロキシ オブジェクトはリクエストをターゲット オブジェクトに転送します。
- ターゲット オブジェクトは特定のビジネス ロジックを実行し、結果を返します。
- プロキシ オブジェクトは、ターゲット オブジェクトが結果を返す前または後に、いくつかの追加の操作を実行できます。
- プロキシ オブジェクトは結果をクライアントに返します。
プロキシ オブジェクトを導入することにより、プロキシ パターンはターゲット オブジェクトへのアクセスを制御し、ターゲット オブジェクトを変更せずに機能を追加したり、アクセスを制御したりできます。
3. プロキシモードの実装
Java には、プロキシ モードの主な実装として、静的プロキシと動的プロキシの 2 つがあります。
3.1 静的プロキシ
静的プロキシとは、プロキシ オブジェクトとターゲット オブジェクトの間の関係がコンパイル時に決定され、プロキシ クラスが手動でコードを記述することによって実装されることを意味します。静的プロキシでは、プロキシ クラスとターゲット クラスの両方が同じインターフェイスを実装し、プロキシ クラスはターゲット オブジェクトを保持し、メソッド呼び出しの前後に追加の操作を実行します。
静的プロキシは次のように動作します。
- ターゲット インターフェイスとしてインターフェイス (または抽象クラス) を定義すると、ターゲット オブジェクトはこのインターフェイスを実装します。
- ターゲット インターフェイスを実装し、ターゲット オブジェクトへの参照を保持するプロキシ クラスを作成します。
- プロキシ クラスでターゲット インターフェイスのメソッドをオーバーライドし、メソッド呼び出しの前後に必要な追加操作を実行します。
- クライアントはプロキシ オブジェクトを使用してターゲット オブジェクトにアクセスします。
静的プロキシの特徴:
- プロキシクラスを手動で記述する必要があり、作業負荷が比較的大きくなります。
- ターゲット オブジェクトはインターフェイスを実装する必要があります。
- プロキシ クラスとターゲット クラスの関係はコンパイル時に決定され、動的に変更することはできません。
静的プロキシのアプリケーション シナリオ:
- セキュリティ制御: プロキシ クラスは、ターゲット メソッドを呼び出す前に権限チェックなどのセキュリティ制御を実行できます。
- リモート呼び出し: プロキシ クラスはネットワーク通信に関連する詳細をカプセル化できるため、ローカル メソッドを呼び出すのと同じくらい簡単にリモート メソッドを呼び出すことができます。
- パフォーマンス監視: プロキシ クラスは、パフォーマンス監視と統計分析のためにメソッドの実行時間や呼び出し数などの情報を収集できます。
以下は、単純な静的プロキシのサンプル コードです。
// 定义接口
public interface Image {
void display();
}
// 目标类
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading image: " + filename);
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// 代理类
public class ImageProxy implements Image {
private String filename;
private RealImage realImage;
public ImageProxy(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 测试类
public class ProxyPatternDemo {
public static void main(String[] args) {
// 使用代理对象显示图片
Image image = new ImageProxy("test.jpg");
image.display();
// 图片已加载,直接显示,无需重新加载
image.display();
}
}
上の例では、これはターゲット クラスであるImage
インターフェイスであり、画像のロードと表示を担当します。対象クラスへの参照を保持し、必要に応じて対象クラスを作成・使用するプロキシクラスです。静的プロキシの使用を示すために使用されるテスト クラスです。RealImage
ImageProxy
ProxyPatternDemo
テスト クラスでは、まずプロキシ オブジェクトを作成しImageProxy
、display()
画像を表示するメソッドを呼び出します。display()
プロキシ オブジェクトは実際のオブジェクトを作成しRealImage
、そのメソッドを初めて呼び出したときにそのdisplay()
メソッドを呼び出して画像をロードして表示します。その後メソッドが呼び出された場合display()
、プロキシ オブジェクトはdisplay()
イメージを再読み込みせずに実際のオブジェクトのメソッドを直接呼び出します。
静的プロキシの欠点は、プロキシ クラスを手動で記述する必要があり、作業負荷が比較的大きいことです。インターフェース内のメソッドが多かったり、変更が頻繁にある場合は、プロキシクラスのコードを頻繁に修正する必要があり、メンテナンスの難易度が高くなります。また、プロキシ クラスと静的プロキシのターゲット クラスの間には密結合関係があるため、ターゲット クラスが変更されると、それに応じてプロキシ クラスも変更する必要があります。
静的プロキシの欠点を解決するために、Java は動的プロキシ メカニズムも提供します。
3.2 動的プロキシ
動的プロキシとは、プロキシ クラスを手動で作成せずに、実行時にプロキシ オブジェクトを生成することを指します。Java の動的プロキシ機構はリフレクションに基づいて実装されており、動的プロキシはjava.lang.reflect.Proxy
クラスとインターフェイスを使用して実現されます。java.lang.reflect.InvocationHandler
動的プロキシでは、プロキシ クラスの作成とメソッドの呼び出しは実行時に行われます。プロキシ オブジェクトはメモリ内に動的に作成され、ターゲット オブジェクトの参照を保持しながら、ターゲット オブジェクトのインターフェイスを実装します。メソッドが呼び出されると、プロキシ オブジェクトはInvocationHandler
インターフェイス内のメソッドを呼び出してリクエストを処理し、メソッド呼び出しの前後に追加の操作を実行できます。
動的プロキシは次のように機能します。
- インターフェースをターゲットインターフェースとして定義します。
InvocationHandler
メソッド呼び出しの処理と追加の操作の実行を担当するインターフェイスの実装クラスを作成します。Proxy
クラスの静的メソッドを使用して、newProxyInstance()
対象オブジェクトと を指定してプロキシ オブジェクトを生成しますInvocationHandler
。- クライアントはプロキシ オブジェクトを使用して、ターゲット オブジェクトのメソッドにアクセスします。
ダイナミックプロキシの特徴:
- プロキシ クラスを手動で記述する必要はなく、プロキシ オブジェクトは実行時に動的に生成されます。
- ターゲット オブジェクトはインターフェイスを実装する必要はなく、ターゲット オブジェクトの共通インターフェイスを定義するだけで済みます。
- プロキシ オブジェクトとターゲット オブジェクトの関係は実行時に決定され、動的に変更できます。
動的プロキシのアプリケーション シナリオ:
- AOP (アスペクト指向プログラミング): 動的プロキシは、ターゲット メソッドの実行の前後に、アクセス許可のチェックやトランザクション管理などの追加の操作を実行できます。これは AOP を実装する一般的な方法です。
- 遅延読み込み: 遅延読み込みの効果を実現するために、ターゲット メソッドが呼び出されたときに動的プロキシを読み込み、初期化できます。
- ログ: 動的プロキシは、ターゲット メソッドの実行前後のログ情報を記録できます。
単純な動的プロキシのサンプル コードを次に示します。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
interface Image {
void display();
}
// 目标类
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading image: " + filename);
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// InvocationHandler 实现类
class ImageInvocationHandler implements InvocationHandler {
private Object target;
public ImageInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method: " + method.getName());
return result;
}
}
// 测试类
public class ProxyPatternDemo {
public static void main(String[] args) {
// 创建目标对象
Image realImage = new RealImage("test.jpg");
// 创建 InvocationHandler 实例
ImageInvocationHandler handler = new ImageInvocationHandler(realImage);
// 创建代理对象
Image imageProxy = (Image) Proxy.newProxyInstance(Image.class.getClassLoader(),
new Class[]{
Image.class}, handler);
// 使用代理对象显示图片
imageProxy.display();
}
}
上の例では、これはターゲット クラスであるImage
インターフェイスであり、画像のロードと表示を担当します。メソッド呼び出しを処理し、追加の操作を実行するために使用されるインターフェイスの実装クラスです。は、動的プロキシの使用を示すテスト クラスです。RealImage
ImageInvocationHandler
InvocationHandler
ProxyPatternDemo
テスト クラスでは、まずターゲット オブジェクトを作成しRealImage
、次にImageInvocationHandler
インスタンスを作成して、ターゲット オブジェクトをコンストラクターに渡します。Proxy
次に、クラスのメソッドを呼び出してプロキシ オブジェクトを生成しますnewProxyInstance()
。最後に、プロキシ オブジェクトを使用して、display()
画像を表示するメソッドを呼び出します。
メソッドが呼び出されると、プロキシ オブジェクトはInvocationHandler
インターフェイス内のメソッドを呼び出してinvoke()
、メソッド呼び出しを処理します。この例では、invoke()
メソッド内にメソッド名を出力する追加の操作を実装し、リフレクションを通じてターゲット オブジェクトのメソッドを呼び出します。