sugarlife's blog

Javaとか、http://twitter.com/sugarlife

Google's Guava Libraries で EventBus

この記事は Java Advent Calendar 2011 の10日目です。wikiばかりで全くblogを書かないので企画の力に頼らせていただきました!
昨日の記事 << | >>明日の記事

今日はGoogleJavaユーティリティライブラリであるGuava Librariesの最新版 10.0.1 で、新たにパッケージごと追加されたAPIであるEventBusについて紹介します。このAPIJavaDocに@Betaアノテーションが付いており、ベータ機能であるため、将来的に内容が変更される可能性があります。

EventBusって?

レイヤー分割されたコンポーネントで構成されたシステムでは、上位層から下位層を呼び出す場合はメソッドを直接呼出し、下位層から上位層を呼び出す場合は相互依存を避けるためイベント通知の機構(publish-subscribeモデル、コールバック)を利用することが多くあります。この機構としてはオブジェクトの内部状態を観察するObserverパターンが著名です。
このような機構のJavaによる実装としては、Observerパターンの実装(java.util.Observerjava.util.Observable)や、SWTに代表されるイベント通知モデルの作りこみがあります。しかし、継承や実装の制約、イベント通知種類(ボタンクリック、スライドバー移動...)ごとのリスナーの管理が必要など多大な手間が懸かります。また、同一レイヤ間の呼び出しにも、疎結合を保つためにイベント通知機構を利用したいと考える時があります。
EventBusとは、このような publish-subscribe モデルをシンプルに実装する1つの手段です。過去には、JavaBeansの連携手法の1つとしてhttp:/eventbus.org で公開されていたと聞いていたのですが、消えてるだと...? 多分こちら(java.net/projects)に移られたのだと思いますが、同じものかは解りません。

長々と書きましたが、纏めるとシンプルにイベント駆動が書けます。一番新しいGoogleさんのEventBusならかなり簡単に書けるのではないだろうか?!というわけで試してみました。

EventBus@Guava Libraries

シンプルなサンプル

簡単に何かしらの文章を発行するイベントの駆動を書いてみます。

イベントを受け取るクラスとメソッド(event handler)は以下のように書けます。

import com.google.common.eventbus.Subscribe;
public class Subscriber {
	@Subscribe
	public void getMessage(String msg){ 
		// @Subscribeのメソッドは必ずpublic, 引数は1つのみ。
		System.out.println("I got a message:" + msg);
	}
}

@Subscribeアノテーションを使うことでevent handlerとなる公開メソッドを実装することができます。イベントである引数は必ず1つのみであることに注意が必要です。このevent handlerメソッドを持つクラスを登録し、イベントを流すのは次のように書きます。

import com.google.common.eventbus.EventBus;
public class Publisher {
	public static void main(String[] args) {
		EventBus bus = new EventBus();
		// EventBusにSubscribe(event handler)を登録。
		bus.register(new Subscriber());
		// EventBusにイベントを流す。
		bus.post("Java Advent Calendar [12/10]");
	}
}

Publisherを実行すると、I got a message:Java Advent Calendar [12/10]と出力されます。

DeadEvent

EventBusに流したイベントがどこにも受け取られなかった場合、DeadEventとして再度流されます。このDeadEventを受け取るSubscriberを実装することで、誰にも受け取られなかった悲しいイベントを受け取ることができます。

import com.google.common.eventbus.DeadEvent;
import com.google.common.eventbus.Subscribe;
public class DeadEventSubscriber {
	@Subscribe
	public void getDeadEvent(DeadEvent event){
		System.out.println("I got a DeadEvent:" + event.getEvent().toString());
	}
}

先ほどのPublisherにこのSubscriberを登録して、誰にも受け取られないイベントを流してみます。

import com.google.common.eventbus.EventBus;
public class Publisher {
	public static void main(String[] args) {
		EventBus bus = new EventBus();
		bus.register(new Subscriber());
		bus.register(new DeadEventSubscriber()); // 追加
		bus.post("Java Advent Calendar [12/10]");
		bus.post( new Integer(0) ); // 追加
	}
}

実行すると、

I got a message:Java Advent Calendar [12/10]
I got a DeadEvent:0

と、出力されます。デバッグやエラー処理に利用することができるので便利ですね。

その他

非同期に実行するにはAsyncEventBusを利用することで実現できます。

おわりに

Guava LibrariesのEventBusを使ってみました。思ったよりもかなり簡単にイベント駆動が書けますね!
実際に運用するには、Subscriberに親子関係にあるイベントを受け取るようなevent handlerメソッド(getMessage(String)とgetMessage(Object)など)を複数書いたらどうなるんだ(環境依存..?)とか、AsyncEventBusの使い方、スレッドセーフに記述するにはどうするのか、性能は?等、調べるところは多いのですが、後々機会があれば書いていきたいと思います。

以上、Java Advent Calendar 10日目でした!次はs_kozakeさんの番です!