きっかけ
JavaのAdvent Callendar で、Proxyクラスについて触れている記事があった。
そういえば、試したことが無かったので、暇な年始に試してみた。
実験
環境
java
$java -version openjdk version "11.0.1" 2018-10-16 OpenJDK Runtime Environment 18.9 (build 11.0.1+13) OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)
os
$sw_vers ProductName: Mac OS X ProductVersion: 10.14.2 BuildVersion: 18C54
実装
今回、結構てこずった。。。。
何にてこずったかと言うと、必要となるクラスが思ったより多かったので、それの役割を理解するのが辛かった。。。
Javaのリフレクションの動きは分かっていたが、クラスが増えると、意外と混乱する。
あと、Javaから離れていたせいか、staticメソッドの制約を忘れていて、ドツボにハマった。。。
まずは、必要となるクラスの説明から
- Main:実験用に呼び出すクラス
- IFの定義(interface):今回のテスト対象のクラスのIF定義。なくてもいい気もするが、実装の隠蔽のためのプロキシなので、実際に使われる際の状況を意識するために用意
- 呼び出すクラス:IF定義を実装したクラス。こいつを呼び出す。
- Proxy:代理呼び出しの実装をするクラス。
- Handler:呼び出すクラスとプロキシの間を中継するクラス。
では、実際に実装へ
IF定義
public interface Driveintori { public String getStoreName(); }
みんな大好き、ドラ鳥をテーマに実装していきます。
ドラ鳥ってなんだ?って思った人は、「ドライブイン鳥」、もしくは、「ゾンビランドサガ ドラ鳥」で検索してね。
呼び出すクラス
public class DriveintoriImpl implements Driveintori { @Override public String getStoreName() { return "ドライブイン鳥 伊万里店"; } }
伊万里店として実装。
Proxy & Handler
ここが一番重要。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DriveintoriProxy { private Driveintori store; private Object proxy; private DriveintoriProxy(Driveintori store) { this.store = store; this.proxy = Proxy.newProxyInstance(Driveintori.class.getClassLoader(), new Class[] { Driveintori.class }, new DriveintoriHandler()); } public static Driveintori createProxy(Driveintori store) { DriveintoriProxy dp = new DriveintoriProxy(store); return Driveintori.class.cast(dp.proxy); } /** * Proxyのメソッド呼び出しハンドラ. */ private class DriveintoriHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Let's go Doratori!!"); Object o = method.invoke(store, args); System.out.println("comming agein Doratori!"); Class type = method.getReturnType(); return type.cast(o); } } }
DriveintoriProxyのコンストラクタで、Proxy.newProxyInstanceしているのが実際のプロキシの生成処理。
一番厄介なのは、Objectで返すところ。
本当は、引数で指定したclassの型を返して欲しいのだが、何か理由があってこういう実装になったのだろうか?
汎用性を意識しすぎて、使いにくい気がしないでもない。
使う側としては、DroiveintoriのIF定義で返して欲しいので、createProxy内でキャストして返してる。
Handlerクラスというのは、DriveintoriHandlerが該当する。
実際に、生成したProxyクラスでメソッド呼び出しをすると、DriveintoriHandlerのinvokeを経由して、Driveintoriの実装クラスであるDriveintoriImplが呼び出される。
Mainクラス
public class Main { public static void main(String[] args) { System.out.println("start"); Driveintori proxy = DriveintoriProxy.createProxy(new DriveintoriImpl()); var name = proxy.getStoreName(); System.out.println("name:" + name); System.out.println("end"); } }
各クラスを繋ぎ合わせて実験。
実行結果
実際に実行してみる。
start Let's go Doratori!! comming agein Doratori! name:ドライブイン鳥 伊万里店 end
ドラ鳥の伊万里店が表示されました。
所感
やっぱり、クラス多くなるのがネック。
クラス図描いていかないと、実際の業務で利用するってなった時に、結構迷いそう。
気になったのは、実際にリフレクションするのと、Proxy使うのとで、アクセス時間に差がでるのかが気になった。
使われそうな箇所としては、ログ周りかなと思う。
あとは、DB接続とかの前処理・後処理が必要となる箇所。
切替が必要そうな箇所は、だいたい適用できるのではないかと思う。
ただ、本当に適用が必要なのかは、よくよく考える必要があるとは思うが。