エンターテイメント!!

遊戯王好きのJavaエンジニアのブログ。バーニングソウルを会得する特訓中。

java.lang.reflect.Proxyクラスを試してみる

きっかけ

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接続とかの前処理・後処理が必要となる箇所。
切替が必要そうな箇所は、だいたい適用できるのではないかと思う。
ただ、本当に適用が必要なのかは、よくよく考える必要があるとは思うが。

参考サイト

Proxy (Java Platform SE 8)

java.lang.reflect.Proxyの使い方(1) - Qiita