汎用の注釈駆動型イベント通知フレームワーク
-
05-07-2019 - |
質問
Javaのシンプルでインターフェイス駆動型のイベント通知フレームワークは、カンブリア紀以前から存在していましたが(例:java.beans.PropertyChangeSupport)、代わりにアノテーション駆動型のイベント通知を使用するフレームワークで人気が高まっています。
例については、 JBossCache 2.2 。リスナークラスには、リジッドインターフェイスに準拠するのではなく、アノテーション付きのリスナーメソッドがあります。興味のないリスナーコールバックの空の実装を記述する必要がないため、プログラミングが簡単になり、読みやすくなります(そして、はい、リスナーアダプターのスーパークラスについて知っています)。
JBossCacheドキュメントのサンプル:
@CacheListener
public class MyListener {
@CacheStarted
@CacheStopped
public void cacheStartStopEvent(Event e) {
switch (e.getType()) {
case Event.Type.CACHE_STARTED:
System.out.println("Cache has started");
break;
case Event.Type.CACHE_STOPPED:
System.out.println("Cache has stopped");
break;
}
}
@NodeCreated
@NodeRemoved
@NodeVisited
@NodeModified
@NodeMoved
public void logNodeEvent(NodeEvent ne) {
log("An event on node " + ne.getFqn() + " has occured");
}
}
これに関する問題は、この種のことをサポートするためのフレームワークを記述する非常に多くの関与するプロセスであるということです、それは注釈反射の性質のためです。
それで、一般的なフレームワークを書く道に進む前に、誰かがすでにそれをやっていることを望んでいました。誰かがそのようなことに出くわしましたか?
解決
EventBus を使用すると、今日これを既に実行できます。
次の例は、 EventBusスタートガイドからのものです。発行されたイベントに基づいて更新されるステータスバー。ステータスバーコントロール/ウィジェットをパブリッシャーのリスナーとして登録する必要はありません。 EventBusを使用しない場合、ステータスバーを多くのクラスのリスナーとして追加する必要があります。ステータスバーはいつでも作成および破棄できます。
public StatusBar extends JLabel {
public StatusBar() {
AnnotationProcessor.process(this);
}
@EventSubscriber(eventClass=StatusEvent.class)
public void updateStatus(StatusEvent statusEvent) {
this.setText(statusEvent.getStatusText();
}
}
同様のプロジェクトは ELF(Event Listener Framework)ですが、それほど成熟していないようです。
現在、パブリッシュ/サブスクライブイベントドリブンプログラミング| KevのSpring vs Java EE Dev およびフォローアップ記事。
他のヒント
この種の注釈ベースのイベントを処理するために、 http://neoevents.googlecode.com を作成しましたハンドラ。
@actionPerformed
private void onClick() {
//do something
}
protected void initComponents() {
JButton button = new JButton("Click me!!!");
button.addActionListener(new ActionListener(this) );
}
それは私が期待していたようにシンプルに見えます。注釈は、J2SEのすべてのリスナーで使用できます。
複雑と勘違いしないでください。これは次のように思えます:
- デバッグする悪夢
- フォローするのが難しい(保守の観点から、または誰かが6か月後に何かを変更しようとする)
- コードのような
if(NodeCreatedEventのイベントインスタンス)
でいっぱい。これがadapter
をサブクラス化するよりも優れている理由はわかりません!
ここで見た主な問題は、どのメソッドがどのイベントに実際に使用できるかを制限するメソッドパラメーターです。そのためのコンパイル時のヘルプはありません。
これは、Javaイベントモデルのようなオブザーバーパターンの実装にとって、インターフェイスを魅力的なものにします。 eclipseなどのツールはメソッドスタブを自動生成できるため、署名を間違えることはありません。あなたの例では、間違ったパラメータータイプを使用することは非常に簡単であり、イベントが発生するまでそれを知ることはありません(これは数か月先のエラーの場合があります)
試してみたいことの1つは、注釈と&オブザーバとnullオブジェクトの実装を実装するプロセッサ。あなたが持っていると仮定します
package a.b.c;
public interface SomeListener {
void fee();
void fie();
void fo();
void fum();
}
そしてリスナーインスタンスを作成したかった。書くことができます
package x.y.z;
import a.b.c.SomeListener;
import com.javadude.annotation.Bean;
import com.javadude.annotation.NullObject;
@Bean(nullObjectImplementations = {@NullObject(type = SomeListener.class) })
public class Foo extends FooGen implements SomeListener {
@Override
public void fie() {
// whatever code you need here
}
}
これらのイベントのソースを作成するには、次のように記述できます
package a.b.c;
import com.javadude.annotation.Bean;
import com.javadude.annotation.Observer;
@Bean(observers = {@Observer(type = SomeListener.class)})
public class Source extends SourceGen {
// SourceGen will have add/remove listener and fire methods
// for each method in SomeListener
}
http://code.google.com/p/javadude/wiki/をご覧ください。関心がある場合は注釈。他にもいくつかのアイデアがあります。
私は、一般的な注釈駆動型のイベントフレームワークについても考えてきました。静的型付けによって提供される利点は気に入っていますが、現在のインターフェイス駆動型のイベントモデルを使用するのは苦痛です(コードがugい)。カスタムアノテーションプロセッサを使用して、コンパイル時のチェックを行うことは可能でしょうか?これにより、不足している「安全性」の一部が追加される場合があります。私たちは皆、慣れ親しんでいます。
多くのエラーチェックは、リスナーが「登録」されたときにも実行できます。イベントプロデューサーと。そのため、おそらく起動時にさえ、アプリケーションは早期に(リスナーが登録されると)失敗します。
これは、私がいじくり回してきた汎用フレームワークがどのように見えるかの例です:
public class ExampleProducer {
private EventSupport<ActionEvent> eventSupport;
public ExampleProducer() {
eventSupport = new EventSupport<ActionEvent>(this);
}
@AddListenersFor(ActionEvent.class)
public void addActionListener(Object listener)
{
eventSupport.addListener(listener);
}
@RemoveListenersFor(ActionEvent.class)
public void removeActionListener(Object listener)
{
eventSupport.removeListener(listener);
}
public void buttonClicked() {
eventSupport.fire(new ActionEvent(this,
ActionEvent.ACTION_PERFORMED, "Click"));
}
}
プロデューサーは、リフレクションを使用してイベントを呼び出す EventSupport
を使用します。前述したように、 EventSupport
は、イベントリスナーが登録されたときに初期チェックを実行できます。
public class ExampleListener
{
private ExampleProducer submitButton;
public ExampleListener()
{
submitButton = new ExampleProducer();
EventSupport.autoRegisterEvents(this);
}
@HandlesEventFor("submitButton")
public void handleSubmitButtonClick(ActionEvent event)
{
//...some code to handle the event here
}
}
ここで、 EventSupport
には、リフレクションを使用してリスナーをイベントプロデューサーに自動登録する静的メソッドがあります。これにより、イベントソースに手動で登録する必要がなくなります。カスタムアノテーションプロセッサを使用して、 @HandlesEventFor
アノテーションが ExampleListener
の実際のフィールドを参照していることを検証できます。アノテーションプロセッサは、イベントハンドラメソッドのシグネチャが ExampleProducer
の登録メソッドのいずれかと一致することを確認するなど、他のチェックも実行できます(基本的に、登録時に実行できる同じチェック時間)。
あなたはどう思いますか?これは完全な開発に時間をかける価値がありますか?
SJES という同様のプロジェクトがあります。
public class SomeController {
private Calculator c1 = new Calculator();
private Calculator c2 = new Calculator();
public SomeController() {
c1.registerReceiver(this);
c2.registerReceiver(this);
c1.add(10, 10);
c2.add(20, 20);
}
@EventReceiver(handleFor="c1")
public void onResultC1(Calculator.Event e) {
System.out.println("Calculator 1 got: " + e.result);
}
@EventReceiver(handleFor="c2")
public void onResultC2(Calculator.Event e) {
System.out.println("Calculator 2 got: " + e.result);
}
@EventReceiver
public void onResultAll(Calculator.Event e) {
System.out.println("Calculator got: " + e.result);
}
}
public class Calculator {
private EventHelper eventHelper = new EventHelper(this);
public class Event {
long result;
public Event(long result) {
this.result = result;
}
}
public class AddEvent extends Event {
public AddEvent(long result) {
super(result);
}
}
public class SubEvent extends Event {
public SubEvent(long result) {
super(result);
}
}
public void unregisterReceiver(Object o) {
eventHelper.unregisterReceiver(o);
}
public void registerReceiver(Object o) {
eventHelper.registerReceiver(o);
}
public void add(long a, long b) {
eventHelper.fireEvent(new AddEvent(a + b));
}
public void sub(long a, long b) {
eventHelper.fireEvent(new SubEvent(a - b));
}
public void pass(long a) {
eventHelper.fireEvent(new Event(a));
}
}
これは非常に使いやすいと思います。
MBassador も確認できます。これは注釈駆動型で、非常に軽量で、弱参照(したがって、オブジェクトのライフサイクル管理がspringやguiceまたはsomethignなどのフレームワークによって行われる環境に簡単に統合できます。
これは、オブジェクトフィルタリングメカニズムを提供します(したがって、NodeEventにサブスクライブし、特定のタイプのセットのみにメッセージ処理を制限するためにいくつかのフィルターをアタッチできます)。 独自の注釈を定義して、ハンドラーの宣言をカスタマイズすることもできます。
また、非常に高速でリソース効率が高いです。このベンチマークをご覧ください。Guavaまたはmbassadorを使用したさまざまなシナリオのパフォーマンスグラフが表示されています。