Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

guava librariesに追加されたEventBusについて

今までguavaのr09を使っていたのですが、先月末にRelease10.0が出ていたようなので早速使ってみました。
新機能として目立つのはCacheとEventBus。Cacheはなんとなく使い道が分かりそうなのでEventBusについて調べたところ、シンプルで使いやすそうなイベント駆動基盤でした。面白かったので、使い方と注意点を簡単にまとめておきます。

他のイベント駆動基盤との違い

javadocにまとまってます。
私自身は他の基盤に詳しくないので、差別化できているかどうかわかりませんでした。少なくともSwingライクなイベント処理よりはキレイに書けそうな印象です。

使い方

  1. EventBusのインスタンスを作る
  2. EventBusのインスタンスにリスナーをregister
  3. EventBusにイベントをpost

基本的にはこれだけです。ただしイベントやリスナーはインタフェースとして用意されているわけではありません。

リスナーはどのようなクラスでもよく、メソッドを@Subscribeアノテーションで修飾するだけで使えます。例えば以下のクラスはIntegerインスタンスをlistenするリスナーとして使えます。

import com.google.common.eventbus.Subscribe;

public class IntegerListener {
	@Subscribe
	public void listen(Integer i) {
		System.out.println("Integer is posted.");
	}
}

ここで注目したいのは、イベントとしてフツーのIntegerを使っていることです。このようにguavaのEventBusでは、好きな型をイベントとして扱えます。イベントの発火は以下のようになります。

import com.google.common.eventbus.EventBus;

public class EventBusSample {
	public void postInteger() {
		EventBus eventBus = new EventBus();
		eventBus.register(new IntegerListener());
		eventBus.post(Integer.valueOf(0)); //=> "Integer is posted."
	}
}

注意点

リスナーが@Subscribeで修飾されたメソッドを複数個持っている場合、これらが実行される順番は環境依存となります。

これはAnnotatedHandlerFinder#findAllHandlers()が呼び出すClass#getMethods()が、返り値の並び順について何も規定していないことに因ります。実際以下のサンプルコードは、OpenJDK7とMac OSXデフォルトのJDK6で異なる結果を出力します。

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

public class EventBusSample {
	@Subscribe
	public void listen(Object i) {
		System.out.println("Object is posted.");
	}

	@Subscribe
	public void listen(Integer i) {
		System.out.println("Integer is posted.");
	}

	public static void main(String[] args) {
		new EventBusSample().execute();
	}

	private void execute() {
		EventBus eventBus = new EventBus();
		eventBus.register(this);
		eventBus.post(Integer.valueOf(0));
	}
}

ですのでメソッドの実行順が重要な意味を持つ場合は、同一クラス内に@Subscribeで修飾されたメソッドを複数個作らない方が良いでしょう。少なくとも特定の型とその親クラスに反応するメソッドは、1つだけに限定したほうがよさそうです。