プロキシパターンの重要性
Spring Frameworkの主要機能の一つであるAOP(アスペクト指向プログラミング)を理解する上で、プロキシパターンの習得は不可欠です。プロキシパターンは、あるオブジェクトへのアクセスを制御したり、機能を追加したりするために、そのオブジェクトの「代理」となるオブジェクトを提供する設計手法です。
プロキシパターンは大きく分けて以下の2つの形態が存在します。
- 静的プロキシ: コンパイル時にプロキシクラスを作成する手法。
- 動的プロキシ: 実行時に動的にプロキシクラスを生成する手法。
1. 静的プロキシの実装
静的プロキシでは、インターフェースを定義し、その実装クラス(実体)と、実体をラップするプロキシクラスを作成します。プロキシクラスは実体と同じインターフェースを継承し、内部に実体の参照を持ちます。
役割構成
- 抽象ロール: 共通の機能を定義するインターフェース。
- 実体ロール: 実際のビジネスロジックを持つクラス。
- プロキシロール: 実体を操作し、前後に付加機能(ログ出力など)を追加するクラス。
- クライアント: プロキシを介してサービスを利用する側。
コード例:データ処理サービスへのログ追加
// 抽象ロール
public interface DataProcessor {
void executeTask(String taskName);
}
// 実体ロール
public class DataProcessorImpl implements DataProcessor {
@Override
public void executeTask(String taskName) {
System.out.println("実体:タスク「" + taskName + "」を実行しています。");
}
}
// プロキシロール
public class DataProcessorProxy implements DataProcessor {
private DataProcessor target;
public DataProcessorProxy(DataProcessor target) {
this.target = target;
}
@Override
public void executeTask(String taskName) {
logBefore();
target.executeTask(taskName);
logAfter();
}
private void logBefore() {
System.out.println("[LOG] 処理を開始します。");
}
private void logAfter() {
System.out.println("[LOG] 処理が完了しました。");
}
}
この手法の利点は、既存のコード(DataProcessorImpl)を修正することなく、機能拡張(ログ出力)ができる点にあります。しかし、対象となるクラスが増えるたびにプロキシクラスを作成する必要があり、コードの重複が発生しやすいという欠点があります。
2. 動的プロキシの実装
動的プロキシは、リフレクションを利用して実行時にプロキシオブジェクトを生成します。これにより、静的プロキシのようなクラスの量産を避けることができます。
Javaにおいては主に以下の2種類が利用されます。
- JDK動的プロキシ: インターフェースに基づいたプロキシ(標準機能)。
- CGLIB: クラス継承に基づいたプロキシ(サードパーティライブラリ)。
JDK動的プロキシの実装例
JDK動的プロキシを利用するには、InvocationHandlerインターフェースを実装したハンドラクラスを作成します。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class PerformanceHandler implements InvocationHandler {
private Object target;
public PerformanceHandler(Object target) {
this.target = target;
}
// プロキシインスタンスの生成
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
// 元のメソッドの実行
Object result = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println(method.getName() + " の実行時間: " + (end - start) + "ms");
return result;
}
}
クライアント側での利用
public class Main {
public static void main(String[] args) {
// 実体の作成
DataProcessor realService = new DataProcessorImpl();
// ハンドラの作成とプロキシの取得
PerformanceHandler handler = new PerformanceHandler(realService);
DataProcessor proxy = (DataProcessor) handler.getProxy();
// プロキシ経由で実行
proxy.executeTask("レポート作成");
}
}
仕組みの詳細
- Proxy.newProxyInstance:
- 第一引数:クラスローダー。
- 第二引数:プロキシが実装すべきインターフェースの配列。
- 第三引数:
InvocationHandlerの実装。
- invokeメソッド: プロキシ経由でメソッドが呼ばれると、このメソッドに集約されます。ここで、メソッド実行前後の共通処理を柔軟に記述できます。
動的プロキシを活用することで、業務ロジック(ビジネスロジック)と共通処理(ログ、トランザクション管理、セキュリティなど)を明確に分離することが可能となり、Spring AOPの基盤として機能しています。