Commons Proxy を見てみる

id:daisuke-m に「今度 "Apache commons が便利な件 (commons-proxy 編) " を書いて Jiemamy にコントリビュートしろ」と言われたので、とりあえず Commons Proxy って何やねん、ってとこから見ていこうと思います。


Commons Proxy とは

Commons Proxy 公式サイトの冒頭部分で下記のように書いてありました。

Proxy デザインパターン (GoF) はあなたに「他のオブジェクトの肩代わり、もしくは代替物としてそのオブジェクトのアクセスをコントロールすること」を可能にします。プロキシは多くの場面で使うことができます。たとえば・・・

  • 初期化の先送り - プロキシは元の実装の代役のように振る舞い、必要なときだけ元の実装のインスタンスを作成することができます。
  • セキュリティ - プロキシオブジェクトは (EJB のように) ユーザがあるメソッドの実行が本当にできるのか確認することができます。
  • ロギング - プロキシはあらゆるメソッド実行のログをとることができ、デバッグに必要な情報をもたらすことができます。
  • パフォーマンスの監視 - プロキシはシステム管理者にシステムをダウンさせる潜在的なものが何なのかを明らかにさせることができるパフォーマンスの監視を行うため、それぞれのメソッド実行のログをとることができます。

Commons Proxy はプロキシファクトリやオブジェクトプロバイダ、インボーカー、インターセプタを使うことで動的なプロキシの生成をサポートしています。

Commons Proxy -

なんかいろいろややこしいこと書いてるけど、とにかく Commons Proxy を使うことで動的なプロキシを作ることができるから、GoF の Proxy パターンが簡単に実現できるよ!ってことを言いたいんですよね。おそらく。

何はともあれ、実際に動かしてみる。

何をするにも、まずは Commons Proxy をダウンロードしてこなくちゃいけないですね。さくっと Apache Download Mirrors からダウンロードしちゃいましょう。
Maven2 を使うなら、pom.xml の <dependencies> に以下の記述を行えば OK 。

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-proxy</artifactId>
  <version>1.0</version>
</dependency>

まずは一番基本的なところから。実装のインスタンス生成を遅らせてみようと思います。
たとえば、下のようなプログラムがあったとします。

public class Main {

  public static void main(String[] args) {
    Bean bean = new BeanImpl();
    System.out.println("Call sayHello()");
    bean.sayHello();
  }

}
public interface Bean {

  void sayHello();

}
public class BeanImpl implements Bean {

  public BeanImpl() {
    System.out.println("Instantiated.");
  }

  @Override
  public void sayHello() {
    System.out.println("Hello, world!");
  }

}

実行するまでもないですが、このプログラムを実行すると、下記のような出力が得られます。

Instantiated.
call sayHello()
Hello, world!

さて、これは簡単なプログラム例ですが、BeanImpl クラスの初期化にものすごく時間がかかる場合はどうでしょう?プログラム起動時にいきなり重くなってイライラすること間違いなしですね。必要なときにそういう処理をしろって言いたくなりますよね。
そこで、sayHello() が呼ばれるまでは実際には BeanImpl のインスタンスが生成されないようにしてみましょう。

import org.apache.commons.proxy.ObjectProvider;
import org.apache.commons.proxy.ProxyFactory;
import org.apache.commons.proxy.provider.BeanProvider;

public class Main {

  public static void main(String[] args) {
    ProxyFactory factory = new ProxyFactory();
    ObjectProvider provider = new BeanProvider(BeanImpl.class);
    Bean bean = (Bean) factory.createDelegatorProxy(provider,
        new Class[] { Bean.class });
    System.out.println("call sayHello()");
    bean.sayHello();
  }

}

修正を入れるのは Main クラスだけです。まずプロキシのファクトリである ProxyFactory のインスタンスを生成して、getObject() が呼ばれたら bean を生成する BeanProvider を作ります。その後、ファクトリに対して委譲プロキシを作ります。
こうすると、生成したプロキシに対して初回のメソッドコールがあったときに初めてインスタンスが生成されます。これを実行すると、下記のような出力が得られます。

call sayHello()
Instantiated.
Hello, world!

やりましたね、インスタンスの生成タイミングをずらすことができました。
とりあえず初回はこんな感じで。次回は何をやるかまだ決めてないですが何か書くつもりです。