2010年5月30日日曜日

Guiceについて

Guiceについて 


概要
    Googleが使っているDIに特化したJavaフレームワーク(というか機能)
    フレームワークの定義が”To Do Here” ここでこんなことをしろ に限定されている物の場合、当てはまりません。

    サンプルを一つ作ってみます。
    今回作成するサンプルは、
    1.Guiceで「インターフェース HelloService」とそのインターフェースを実装した「クラス HelloServiceImpl」を関連づけ
    2.Guice経由で、 HelloService インターフェースの何らかの実装を使うクラス MyObject のインスタンスを作成
    
    MyObjectクラス内のHelloService インターフェースに、HelloServiceImplクラスのインスタンスが組み込まれていることを確認します
    
    ? 何がおこるん?

    通常なら、HelloServiceImplクラスをインスタンス化させて、
    MyObjectのコンストラクタに入れてMyObjectをインスタンス化、でいいんですが、
    ここでは、HelloServiceImpl、HelloService、MyObjectについての一切のnew演算子を使いません。    

    Guiceがそのへんの肩代わりをやってくれます。
    The new "new" (あたらしいNew) が見れます。
    
    

バージョン
    今回は2.0を使用


使用するための準備
    1.ダウンロード
        下記からguice-2.0.zipとかを持ってくる。

        zip中には、下記が入っているはず。
            COPYING.txt
            aopalliance.jar
            guice-2.0.jar
            guice-assistedinject-2.0.jar
            guice-jmx-2.0.jar
            guice-jndi-2.0.jar
            guice-multibindings-2.0.jar
            guice-servlet-2.0.jar
            guice-spring-2.0.jar
            guice-struts2-plugin-2.0.jar
            guice-throwingproviders-2.0.jar
            javadoc(フォルダ)


    2.Guice関連のJarをビルドパスに入れる
        今回のサンプルでは下記だけを使う。

            guice-2.0.jar
            aopalliance.jar

環境
     今回はクライアントサイド、ただのJavaApplicationとします。
    サーバサイドでは動かない、完全にローカルのJavaで動くもの。
    Java5以上。
    


サンプルのコーディング
    HelloServiceという、とりあえずHelloWorld的な事をするものをサンプルで作ってみる。
    


Main.java

import com.google.inject.AbstractModule;

import com.google.inject.Guice;

import com.google.inject.Injector;

import com.google.inject.Scopes;


/**

 * エントリーポイントのクラス

 * @author sassembla

 */

public class Main {

    /**

    * main関数

    * @param args

    */

    public static void main(String[] args) {


        //インスタントにInjection処理を記述 AbstractModuleを拡張した別のクラスに書いてもいい。ここでは記述量の問題で逃がしてください。

        Injector injector = Guice.createInjector(new AbstractModule(){

            @Override

            protected void configure() {

                bind(HelloService.class).to(HelloServiceImpl.class).in(Scopes.NO_SCOPE);//スコープ指定で組み込むことができる。これが鬼機能。

            }

        });


        MyObject object = injector.getInstance(MyObject.class);//実行したいオブジェクトにヒョウイさせる

        object.execute();//処理を実行

    }


}




HelloService.java

/**

 * サービス定義のインターフェース

 * 定義インターフェースでは、外部向けの内容をインターフェースとしてまとめておく事で、

 * 見せたくない実装を隠せる。

 * @author sassembla

 *

 */

public interface HelloService {

    /**

     * 名前のみ

     */

    public void sayHello();

}




HelloServiceImpl.java

/**

 * 実装

 * 

 * 抽象物ではなく、実際の処理が行われる部分

 * @author sassembla

 *

 */

public class HelloServiceImpl implements HelloService {

    int count = 0;

    /**

     * とりあえずなにかしら実装

     */

    public void sayHello() {

        System.out.println("Hello, guice!_"+count);

        count++;

    }

}




MyObject.java

import com.google.inject.Inject;

/**

 * 使用したいクラスをヒョウイさせる対象

 * 

 * @author sassembla

 *

 */

public class MyObject {

    private HelloService helloService = null;

    

    @Inject

    public void setHelloService(HelloService helloService) {

        this.helloService = helloService;

    }

    

    public void execute() {

        helloService.sayHello();

    }

}





実行結果
    プリント文出力:Hello, Guice!_0
    動いてる。ナイス。

    実際に動いているという事は、
    MyObjectクラス内のHelloServiceクラスに、HelloServiceImplクラスのインスタンスが組み込まれている
    ということっすね。

    なんで? MyObjectの setHelloService メソッドはどこからも呼ばれて無いじゃん。
    その辺を、
    MyObjectクラスのsetHelloServiceメソッドについてる@Inject アノテーションによってGuiceが「察してくれた」形になってます。 


    Reloで出力してみました。
    
    interface以外関連性無い。

    白矢印はInterface、黒矢印は呼び出し(=メモリーに展開された、ということ)

    InterfaceのHelloServiceと、それを実装(Implements)しているhelloServiceImplとの間の関連はありますが、HelloServiceImplの
    クラス化は見た目行われていません。


利点
    GuiceがhelloServiceImplのインスタンス化や保持を「しれっと」やってくれています。
    ☆Guice(Injector)がインスタンスの作成、保持をしている
        new演算子は、AbstractModule記述以外に全く使用されていません。Guice/Injectorがすべてのインスタンスを保持してくれています。
        
    
    モジュール側(今回はMainがあるクラス)から見ると、HelloService と HelloServiceImplが結びつけられています。
    ☆HelloService さえ実装されてれば、 HelloServiceImpl 以外のクラスでもMyObjectから使える!
    インターフェースを用意して、バインド対象を入れ替えれば、使う側からの区別が消えます。

    ☆コード上に依存関係を記述できる!
        プログラム外の設定コードとさよならできます。 主にクォーテーション、目コピー&手ペーストとさよならです。
     

スコープについて
    bindの際にスコープという要素の設定が出来ます。定義されるのは、bindしたクラスのライフサイクルです。

    Main.javaの中で、下記のような行があります。
    bind(HelloService.class).to(HelloServiceImpl.class).in(Scopes.NO_SCOPE);//スコープ指定で組み込むことができる。これが鬼機能。

    スコープは、Singleton やら Transactionalやら、いろいろ魅力的なものがそろってます。
    NO_SCOPEだと、特に何のフォローもしません。デフォルトはNO_SCOPEです。

    SINGLETONだと、Injectorの寿命が或る限り、あたかもbindしたクラスがシングルトンであるかのように、
    Injector から取得できるクラスが固定されます。
    試しに、スコープをSingletonに変え、Main.java に下記のコードを加えてみると、結果が面白い事になります。

    /~/
    bind(HelloService.class).to(HelloServiceImpl.class).in(Scopes.SINGLETON);//スコープをシングルトンに変更
    /~/
    MyObject object = injector.getInstance(MyObject.class);//実行したいオブジェクトにヒョウイさせる

    object.execute();//処理を実行

    

    //追記

    MyObject object2 = injector.getInstance(MyObject.class);//実行したいオブジェクトにヒョウイさせる

    object2.execute();//こんどはobject2でexecuteしてみよう


    

    結果:

    プリント文出力:Hello, Guice!_0
                         Hello, Guice!_1
                        

    sayHelloが一つのHelloServiceImplから行われた事になっています。
    GuiceがSingletonとしてHelloServiceImplの実装を持ってくれている。
    
    SINGLETONに変えた事により明らかになるのは、
    NO_SCOPE/デフォルトだと、通常のインスタンス作成と変わらない内容だ、という事です。(試しにスコープを戻してみるといい)
    これ、Guiceを通したnewの定義。

    スコープによって、bindしたインスタンスの寿命を設定できます。
    すごく簡単に乱暴に無知に書くと、インスタントに作って使い捨てたければNO_SCOPEで、何度も使いたければSINGLETONで。

    これ以外にも大量に、スコープに関する機能があります。Sessionとか、Transactionとか。 
    それぞれ、どんな寿命をbindしたオブジェクトに与えるのか、なんとなく想像がつくかと思います。
    特にサーバ周りが強烈便利すぎる。

    他人が作った物を使う際に非常に使えます。物理エンジンとか、描画スクリーンとか、
    ステートマシン的なものを使う際には効果絶大です。
    使いたい機能のインターフェースさえ作ってかぶせれば、OKと。 はじめからインターフェースがあればなおさらです。
    

スコープがもたらす副産物
    BoilerCode(湯沸かしコード、とか?)と総称される、シングルトンとして呼びだされてほしいクラスを作るときに
    毎回のように書かなければいけないコードは、書く必要から不要です。 
    書かないでよろしい。 すげー。 
    もうコンストラクタをprivateにしなくていい。
    
    フレームワークとしては初めて、
    ”外見上でなにもしていないように見えてコード記述を管理している機構”なのでは。

    シングルトンか否かは、使う側が設定していい。
    とても素敵。

    ただ、シングルトンを巡る問題は、シングルトンのコードの存在とは別の次元なので、あしからず。


感想
    既存のものと並べてフレームワークと呼ぶ、というよりは、
    インスタンスの依存、保持、ライフサイクルをサポートする機構/機能 です。
    
    アノテーションでの記述は、楽の一言に尽きる。
    これだけ簡単に使えるという事は、他人が書いた物を使う上でも、使用上の制約を低く保てる、という事。
    他人の作った物をどれだけローリスクで限定範囲で使えるか、という部分と、
    また他人が使えるように自分も細分化して物を作れるか、という事の補助になります。

    複数のインスタンスから共通のステートマシンにアクセスする場合などがあるとしたら、
    それはトランザクションがなんとかするのか。

    驚くべきは、GWTでも使える(GIN)、Androidでばっちり動く(Without AOP版)、とかその辺。マジかYO。マジでした。
    今後書き足します。



追記
    GWTとの連携

    Androidとの連携
        without aopであれば使える。
        
    Doja/Starとの連携,,,天変地異とか奇跡が起こったら書くかも。まあ今後、もう出番なさそうだし。
    


このページへのリンク
公開完了 -toru inoue 10/05/30 16:08 









0 件のコメント:

コメントを投稿

フォロワー