0Pricing
Java Academy · レッスン

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 LoggingHandler

InvocationHandler インターフェース

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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. リフレクションによるクラスの調査
  2. メソッドの呼び出しとフィールドへのアクセス
  3. InvocationHandler による動的プロキシ
  4. リフレクションの性能と代替手段
← Java Academyに戻る