InvocationHandler による動的プロキシ
JDK の動的プロキシを作成し、ログ記録、キャッシュ、リトライのためにインターフェースメソッドの呼び出しを横取りします。
「InvocationHandler による動的プロキシ」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
動的プロキシとは
JDK 動的プロキシは、実行時に生成され、1つ以上のインターフェースを実装するオブジェクトです。すべてのメソッド呼び出しは、指定した InvocationHandlerに転送されます。
Proxy.newProxyInstance によるプロキシの作成
Proxy.newProxyInstance(classLoader, interfaces[], handler)を呼び出してプロキシを作成します。プロキシは、指定されたすべてのインターフェースを実装します。
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new LoggingHandler(realService)
);
proxy.findById(42L); // intercepted by LoggingHandlerInvocationHandler インターフェース
InvocationHandlerを実装すると、すべてのメソッド呼び出しを処理できます。ハンドラーには、プロキシオブジェクト、呼び出されたメソッド、引数が渡されます。
public class LoggingHandler implements InvocationHandler {
private final Object target;
public LoggingHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">> " + method.getName());
Object result = method.invoke(target, args);
System.out.println("<< " + method.getName());
return result;
}
}キャッシュのためのプロキシ
InvocationHandler で結果をキャッシュできます。引数に対応する結果がキャッシュにあればそれを返し、なければ実際のメソッドを呼び出して結果を保存します。
public class CachingHandler implements InvocationHandler {
private final Object target;
private final Map<String, Object> cache = new HashMap<>();
public CachingHandler(Object target) { this.target = target; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String key = method.getName() + Arrays.toString(args);
return cache.computeIfAbsent(key, k -> {
try { return method.invoke(target, args); }
catch (Exception e) { throw new RuntimeException(e); }
});
}
}再試行ロジックのためのプロキシ
メソッド呼び出しを再試行ループでラップします。例外が発生した場合は、再スローする前に最大 N 回まで再試行します。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
for (int attempt = 1; attempt <= 3; attempt++) {
try { return method.invoke(target, args); }
catch (InvocationTargetException e) {
if (attempt == 3) throw e.getCause();
Thread.sleep(1000L * attempt);
}
}
throw new RuntimeException("unreachable");
}トランザクション管理のためのプロキシ
Spring の @Transactional は動的プロキシを使用して、メソッド呼び出しの前にトランザクションを開始し、後でコミットまたはロールバックします。呼び出し元はそのことを意識する必要がありません。
オブジェクトがプロキシかどうかの確認
Proxy.isProxyClass(obj.getClass())を使用してプロキシを検出し、Proxy.getInvocationHandler(proxy)でハンドラーを取得します。
if (Proxy.isProxyClass(service.getClass())) {
InvocationHandler h = Proxy.getInvocationHandler(service);
System.out.println("Handler: " + h.getClass().getSimpleName());
}JDK 動的プロキシの制限
JDK 動的プロキシはインターフェースでのみ機能し、具象クラスをプロキシ化できません。クラスベースのプロキシには CGLIB を使用します。Spring も @Configurationクラスに対して内部で CGLIB を使用しています。
代替手段としての CGLIB
CGLIB は実行時に対象クラスのサブクラスを生成し、final ではないすべてのメソッドをオーバーライドします。インターフェースが存在しない場合に Spring が使用します。アノテーションは不要で、透過的に動作します。
Hibernate でのプロキシ
Hibernate は CGLIB/Byte Buddy プロキシを使用してエンティティの関連を遅延ロードします。遅延コレクションにアクセスすると、プロキシが SQL クエリを発行します。
// Hibernate creates a proxy for:
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
// Accessing dept triggers a DB query via the proxyプロキシのパフォーマンス
動的プロキシのメソッドディスパッチは、直接呼び出しより約 5~10 倍遅くなります。非常に頻繁に実行される処理では、動的プロキシの代わりにコンパイル時コード生成(APT)または MethodHandles を検討してください。
理解度チェック
JDK 動的プロキシの主な制限は何ですか?
まとめ
JDK 動的プロキシは、InvocationHandlerを介してインターフェースのメソッド呼び出しをインターセプトします。ログ記録、キャッシュ、再試行、トランザクション管理に使用します。クラスのプロキシ化には CGLIB を使用します。プロキシの生成はリフレクションを伴い、コストがかかるため、キャッシュしてください。
よくある質問
「InvocationHandler による動的プロキシ」レッスンは無料ですか?
はい。「InvocationHandler による動的プロキシ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「InvocationHandler による動的プロキシ」で何を学びますか?
JDK の動的プロキシを作成し、ログ記録、キャッシュ、リトライのためにインターフェースメソッドの呼び出しを横取りします。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「InvocationHandler による動的プロキシ」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- リフレクションによるクラスの調査
- メソッドの呼び出しとフィールドへのアクセス
- InvocationHandler による動的プロキシ
- リフレクションの性能と代替手段