Dev Study
← 解説「動的プロキシ」に戻る

サンプルコードで身につける: 動的プロキシ

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1手で書いた包み役

を使わず、同じを実装した包み役のを手で書いた例です。呼び出す側は Greeter 型として扱うだけなので、包み役に差し替えても気づきません。この方法では、包みたいインターフェースとの数だけ同じようなコードを書くことになります。

Java

2同じことを動的プロキシで行う

包み役のクラスを書かずに、Proxy.newProxyInstance で実行時に作らせた例です。出力の前半は手書きの場合と同じ形になります。後半から、プロキシは Greeter ではあるが RealGreeter ではないこと、java.lang.reflect.Proxy を親に持つ別のクラスであることが分かります。

Java

3作られたクラスの中身

実行時に作られたプロキシのクラスを書き出して で見たものです( 25、 なインターフェースの場合)。greet の本体は、を取り出し、Method と引数の配列を積んで InvocationHandler.invoke を呼び、戻り値を String として返すだけです。前後に何をするかは、すべてハンドラの側に書かれます。

$ javap -p -c 'jdk/proxy1/$Proxy0.class'(JDK 25、抜粋)追跡ログ
public final class jdk.proxy1.$Proxy0 extends java.lang.reflect.Proxy implements demo.PublicGreeter {
...
public final java.lang.String greet(java.lang.String) throws ;
Code:
0: aload_0
1: getfield #19 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #55 // Field m3:Ljava/lang/reflect/Method;
8: iconst_1
9: anewarray #11 // class java/lang/Object
12: dup
13: iconst_0
14: aload_1
15: aastore
16: invokeinterface #25, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
21: checkcast #82 // class java/lang/String
24: areturn

4クラスの名前は条件で変わる

インターフェースの種類を変えて、作られたクラスの名前を出した実測です。public なインターフェースでは jdk.proxy1.$Proxy0、public でないものでは同じの demo.$Proxy1 になり、同じインターフェースで2回作るとクラスは使い回されました。最後の行から、toString の呼び出しもハンドラに届くことが分かります。

$ java demo.ProxyNames(JDK 25、抜粋)コマンド出力
jdk.proxy1.$Proxy0 module=module jdk.proxy1 modifiers=public final interfaces=[interface demo.PublicGreeter]
demo.$Proxy1 module=unnamed module @4554617c modifiers=final interfaces=[interface demo.PackagePrivateGreeter]
jdk.proxy1.$Proxy2 module=module jdk.proxy1 modifiers=public final interfaces=[interface java.lang.Runnable, interface java.lang.Comparable]
jdk.proxy1.$Proxy0 module=module jdk.proxy1 modifiers=public final interfaces=[interface demo.PublicGreeter]
p1 and p4 share one proxy class? true
handled greet
handled toString

5クラスはプロキシにできない

インターフェースの代わりにふつうのクラスを渡すと、IllegalArgumentException になります。下の出力はを捕まえなかった場合の実物で、String のような クラスやを渡しても同じ is not an interface というメッセージでした。インターフェースを持たないクラスを包むには、別の方法が必要になります。

例外を捕まえなかった場合(JDK 25、catch を外した別のクラス Uncaught で実行)コマンド出力
Exception in thread "main" java.lang.IllegalArgumentException: PlainService is not an interface
at java.base/java.lang.reflect.Proxy$ProxyBuilder.validateProxyInterfaces(Proxy.java:608)
at java.base/java.lang.reflect.Proxy$ProxyBuilder.<init>(Proxy.java:560)
...
at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:924)
at Uncaught.main(Uncaught.java:5)
Java

図やコードに出てくる用語(タップで説明)