プロキシパターンの基礎
Javaにおけるプロキシ(代理)とは、対象オブジェクトへのアクセスを仲介するオブジェクトです。プロキシは対象と同一のインターフェースを実装し、メソッド呼び出し前後で追加処理を挿入できます。これにより、本来の処理に影響を与えずに機能強化を実現します。
動的プロキシの概要
動的プロキシは実行時にプロキシクラスを生成する手法で、特にJDKが標準で提供する動的プロキシ機能は広く利用されています。以下の2つのコンポーネントが核となります:
- Proxyクラス(
java.lang.reflect.Proxy):動的プロキシインスタンスを生成 - InvocationHandlerインターフェース(
java.lang.reflect.InvocationHandler):プロキシの振る舞いを定義
静的プロキシと動的プロキシの対比
静的プロキシは手動でプロキシクラスを作成するためシンプルですが、対象インターフェースが増えるたびにプロキシクラスを追加する必要があります。動的プロキシは単一のInvocationHandler実装で複数のインターフェースに対応可能で、保守性が高まります。
静的プロキシの実装例
// ビジネスインターフェース
public interface DataService {
void processData(String data);
}
// 実際の実装クラス
public class DataServiceImpl implements DataService {
@Override
public void processData(String data) {
System.out.println("データ処理: " + data);
}
}
// 静的プロキシクラス
public class DataServiceStaticProxy implements DataService {
private DataService target;
public DataServiceStaticProxy(DataService target) {
this.target = target;
}
@Override
public void processData(String data) {
System.out.println("[開始] データ処理前ログ");
target.processData(data);
System.out.println("[終了] データ処理後ログ");
}
}
// 実行例
public class StaticProxyDemo {
public static void main(String[] args) {
DataService real = new DataServiceImpl();
DataService proxy = new DataServiceStaticProxy(real);
proxy.processData("重要データ");
}
}
JDK動的プロキシの実装例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyDemo {
public static void main(String[] args) {
DataService realService = new DataServiceImpl();
// 動的プロキシ生成
DataService proxyInstance = (DataService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(),
realService.getClass().getInterfaces(),
new DataServiceInvocationHandler(realService)
);
proxyInstance.processData("動的プロキシ経由のデータ");
}
}
// InvocationHandler実装
class DataServiceInvocationHandler implements InvocationHandler {
private final Object target;
public DataServiceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDK動的プロキシ] 前処理: メソッド名=" + method.getName());
Object result = method.invoke(target, args);
System.out.println("[JDK動的プロキシ] 後処理: 結果返却");
return result;
}
}
設計思想と実装ロジック
JDK動的プロキシの設計思想は「インターフェース指向」に基づきます。プロキシ対象は必ず1つ以上のインターフェースを実装している必要があり、Proxy.newProxyInstance()に以下の3要素を渡します:
- クラスローダー(通常は対象クラスのものを流用)
- 実装するインターフェースの配列
- InvocationHandlerインスタンス
メソッド呼び出しが発生すると、自動的にInvocationHandler.invoke()が呼ばれ、リフレクション経由で本来のメソッドが実行されます。この仕組みにより、プロキシロジックとビジネスロジックが完全に分離されます。
実業務での適用例
- アクセス制御:機密メソッドへの呼び出し前に認可チェックを挿入
- ロギング:メソッドの入出力パラメータや実行時間を自動記録
- トランザクション管理:データベース操作の前後でトランザクション制御を実施
- パフォーマンス計測:メソッド実行時間を計測しボトルネックを可視化
代替技術の比較
CGLIB動的プロキシ
CGLIBはバイトコード操作により対象クラスのサブクラスを動的に生成します。インターフェースが不要で、クラスそのものをプロキシ化できる柔軟性が特徴です。ただし、finalクラスやfinalメソッドはプロキシ化できない制約があります。
使用判断基準
| 条件 | 推奨方式 |
|---|---|
| 対象がインターフェースを実装 | JDK動的プロキシ |
| 対象にインターフェースがない | CGLIB動的プロキシ |
| Spring Framework環境 | 自動選択(デフォルトでJDK、代替でCGLIB) |
CGLIB実装例(簡略版)
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(DataServiceImpl.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> {
System.out.println("[CGLIB] 前処理");
Object result = proxy.invokeSuper(obj, args1);
System.out.println("[CGLIB] 後処理");
return result;
});
DataServiceImpl proxy = (DataServiceImpl) enhancer.create();
proxy.processData("CGLIBプロキシ経由");
}
}
適切なプロキシ方式を選択することで、コードの再利用性と保守性を大幅に向上できます。プロジェクトの要件に応じて、JDK動的プロキシとCGLIBを使い分けることが重要です。