MBassadorの紹介

MBassadorの概要

 

1. 概要

簡単に言えば、MBassadora high-performance event bus utilizing the publish-subscribe semantics.です

メッセージは、サブスクライバーの数やメッセージの使用方法を事前に知らなくても、1つ以上のピアにブロードキャストされます。

2. メーベン依存

ライブラリを使用する前に、mbassadorの依存関係を追加する必要があります。


    net.engio
    mbassador
    1.3.1

3. 基本的なイベント処理

3.1. 簡単な例

メッセージを公開する簡単な例から始めましょう。

private MBassador dispatcher = new MBassador<>();
private String messageString;

@Before
public void prepareTests() {
    dispatcher.subscribe(this);
}

@Test
public void whenStringDispatched_thenHandleString() {
    dispatcher.post("TestString").now();

    assertNotNull(messageString);
    assertEquals("TestString", messageString);
}

@Handler
public void handleString(String message) {
    messageString = message;
}


このテストクラスの上部に、デフォルトのコンストラクターを使用してMBassadorが作成されていることがわかります。 次に、@Beforeメソッドで、subscribe()を呼び出し、クラス自体への参照を渡します。

subscribe(),では、ディスパッチャはサブスクライバに@Handlerアノテーションがないか検査します。

そして、最初のテストでは、dispatcher.post(…).now()を呼び出してメッセージをディスパッチします。これにより、handleString()が呼び出されます。

この最初のテストでは、いくつかの重要な概念を示します。 Any Object can be a subscriber, as long as it has one or more methods annotated with @Handler.サブスクライバーは任意の数のハンドラーを持つことができます。

簡単にするためにサブスクライブするテストオブジェクトを使用していますが、ほとんどの本番シナリオでは、メッセージディスパッチャーはコンシューマーとは異なるクラスになります。

ハンドラーメソッドには、メッセージという1つの入力パラメーターしかなく、チェックされた例外をスローすることはできません。

subscribe()メソッドと同様に、postメソッドは任意のObjectを受け入れます。 このObjectはサブスクライバーに配信されます。

メッセージが投稿されると、メッセージタイプをサブスクライブしているすべてのリスナーに配信されます。

別のメッセージハンドラーを追加して、別のメッセージタイプを送信しましょう。

private Integer messageInteger;

@Test
public void whenIntegerDispatched_thenHandleInteger() {
    dispatcher.post(42).now();

    assertNull(messageString);
    assertNotNull(messageInteger);
    assertTrue(42 == messageInteger);
}

@Handler
public void handleInteger(Integer message) {
    messageInteger = message;
}

予想どおり、_ an _Integerをディスパッチすると、handleInteger()が呼び出されますが、handleString()は呼び出されません。 1つのディスパッチャを使用して、複数のメッセージタイプを送信できます。

3.2. デッドメッセージ

では、ハンドラーがない場合、メッセージはどこに行くのでしょうか? 新しいイベントハンドラーを追加してから、3番目のメッセージタイプを送信しましょう。

private Object deadEvent;

@Test
public void whenLongDispatched_thenDeadEvent() {
    dispatcher.post(42L).now();

    assertNull(messageString);
    assertNull(messageInteger);
    assertNotNull(deadEvent);
    assertTrue(deadEvent instanceof Long);
    assertTrue(42L == (Long) deadEvent);
}

@Handler
public void handleDeadEvent(DeadMessage message) {
    deadEvent = message.getMessage();
}

このテストでは、Integer.の代わりにLongをディスパッチします。handleInteger()handleString()も呼び出されませんが、handleDeadEvent()は呼び出されます。

When there are no handlers for a message, it gets wrapped in a DeadMessage object.Deadmessageのハンドラーを追加したので、それをキャプチャします。

DeadMessageは安全に無視できます。アプリケーションがデッドメッセージを追跡する必要がない場合、それらはどこにも行かないようにすることができます。

4. イベント階層の使用

StringおよびIntegerイベントの送信には制限があります。 いくつかのメッセージクラスを作成しましょう。

public class Message {}

public class AckMessage extends Message {}

public class RejectMessage extends Message {
    int code;

    // setters and getters
}

単純な基本クラスと、それを拡張する2つのクラスがあります。

4.1. 基本クラスMessageの送信

Messageイベントから始めます。

private MBassador dispatcher = new MBassador<>();

private Message message;
private AckMessage ackMessage;
private RejectMessage rejectMessage;

@Before
public void prepareTests() {
    dispatcher.subscribe(this);
}

@Test
public void whenMessageDispatched_thenMessageHandled() {
    dispatcher.post(new Message()).now();
    assertNotNull(message);
    assertNull(ackMessage);
    assertNull(rejectMessage);
}

@Handler
public void handleMessage(Message message) {
    this.message = message;
}

@Handler
public void handleRejectMessage(RejectMessage message) {
   rejectMessage = message;
}

@Handler
public void handleAckMessage(AckMessage message) {
    ackMessage = message;
}

MBassador –高性能パブ/サブイベントバスを発見してください。 これにより、Messagesの使用が制限されますが、タイプセーフティのレイヤーが追加されます。

Messageを送信すると、handleMessage()がそれを受信します。 他の2つのハンドラーはサポートしていません。

4.2. サブクラスメッセージの送信

RejectMessageを送信しましょう:

@Test
public void whenRejectDispatched_thenMessageAndRejectHandled() {
    dispatcher.post(new RejectMessage()).now();

    assertNotNull(message);
    assertNotNull(rejectMessage);
    assertNull(ackMessage);
}

RejectMessageを送信すると、handleRejectMessage()handleMessage()の両方がそれを受信します。

RejectMessageMessage,を拡張するため、RejectMessageハンドラーに加えて、Messageハンドラーがそれを受け取りました。

この動作をAckMessageで確認してみましょう。

@Test
public void whenAckDispatched_thenMessageAndAckHandled() {
    dispatcher.post(new AckMessage()).now();

    assertNotNull(message);
    assertNotNull(ackMessage);
    assertNull(rejectMessage);
}

予想どおり、AckMessageを送信すると、handleAckMessage()handleMessage()の両方がそれを受信します。

5. メッセージのフィルタリング

メッセージをタイプ別に整理することは既に強力な機能ですが、さらにフィルタリングすることもできます。

5.1. クラスとサブクラスのフィルター

RejectMessageまたはAckMessageを投稿すると、特定のタイプのイベントハンドラーと基本クラスの両方でイベントを受信しました。

この型階層の問題は、Messageを抽象化し、GenericMessageなどのクラスを作成することで解決できます。 しかし、この贅沢がない場合はどうなるでしょうか。

メッセージフィルターを使用できます。

private Message baseMessage;
private Message subMessage;

@Test
public void whenMessageDispatched_thenMessageFiltered() {
    dispatcher.post(new Message()).now();

    assertNotNull(baseMessage);
    assertNull(subMessage);
}

@Test
public void whenRejectDispatched_thenRejectFiltered() {
    dispatcher.post(new RejectMessage()).now();

    assertNotNull(subMessage);
    assertNull(baseMessage);
}

@Handler(filters = { @Filter(Filters.RejectSubtypes.class) })
public void handleBaseMessage(Message message) {
    this.baseMessage = message;
}

@Handler(filters = { @Filter(Filters.SubtypesOnly.class) })
public void handleSubMessage(Message message) {
    this.subMessage = message;
}

The filters parameter for the @Handler annotation accepts a Class that implements IMessageFilter。 ライブラリには2つの例があります。

Filters.RejectSubtypesは、その名前が示すように、すべてのサブタイプを除外します。 この場合、RejectMessagehandleBaseMessage().によって処理されないことがわかります

Filters.SubtypesOnlyも、その名前が示すように機能します。つまり、基本タイプをすべて除外します。 この場合、MessagehandleSubMessage().によって処理されないことがわかります

5.2. IMessageFilter

Filters.RejectSubtypesFilters.SubtypesOnlyはどちらもIMessageFilterを実装します。

RejectSubTypesは、メッセージのクラスを定義されたメッセージタイプと比較し、サブクラスとは対照的に、そのタイプの1つに等しいメッセージのみを通過させます。

5.3. 条件付きフィルター

幸いなことに、メッセージをフィルタリングする簡単な方法があります。 MBassador supports a subset of Java EL expressions as conditions for filtering messages.

長さに基づいてStringメッセージをフィルタリングしてみましょう。

private String testString;

@Test
public void whenLongStringDispatched_thenStringFiltered() {
    dispatcher.post("foobar!").now();

    assertNull(testString);
}

@Handler(condition = "msg.length() < 7")
public void handleStringMessage(String message) {
    this.testString = message;
}

「foobar!」メッセージは7文字の長さで、フィルタリングされます。 短いStringを送信しましょう:

@Test
public void whenShortStringDispatched_thenStringHandled() {
    dispatcher.post("foobar").now();

    assertNotNull(testString);
}

現在、「foobar」の長さはわずか6文字であり、パススルーされます。

RejectMessageには、アクセサ付きのフィールドが含まれています。 そのためのフィルターを書いてみましょう。

private RejectMessage rejectMessage;

@Test
public void whenWrongRejectDispatched_thenRejectFiltered() {

    RejectMessage testReject = new RejectMessage();
    testReject.setCode(-1);

    dispatcher.post(testReject).now();

    assertNull(rejectMessage);
    assertNotNull(subMessage);
    assertEquals(-1, ((RejectMessage) subMessage).getCode());
}

@Handler(condition = "msg.getCode() != -1")
public void handleRejectMessage(RejectMessage rejectMessage) {
    this.rejectMessage = rejectMessage;
}

ここでも、オブジェクトのメソッドを照会し、メッセージをフィルタリングするかどうかを選択できます。

5.4. フィルタリングされたメッセージをキャプチャする

DeadEvents,と同様に、フィルタリングされたメッセージをキャプチャして処理したい場合があります。 フィルタリングされたイベントをキャプチャするための専用のメカニズムもあります。 Filtered events are treated differently from “dead” events.

これを説明するテストを書いてみましょう。

private String testString;
private FilteredMessage filteredMessage;
private DeadMessage deadMessage;

@Test
public void whenLongStringDispatched_thenStringFiltered() {
    dispatcher.post("foobar!").now();

    assertNull(testString);
    assertNotNull(filteredMessage);
    assertTrue(filteredMessage.getMessage() instanceof String);
    assertNull(deadMessage);
}

@Handler(condition = "msg.length() < 7")
public void handleStringMessage(String message) {
    this.testString = message;
}

@Handler
public void handleFilterMessage(FilteredMessage message) {
    this.filteredMessage = message;
}

@Handler
public void handleDeadMessage(DeadMessage deadMessage) {
    this.deadMessage = deadMessage;
}

FilteredMessageハンドラーを追加すると、長さのためにフィルター処理されたStringsを追跡できます。 filterMessageには長すぎるStringが含まれていますが、deadMessagenull.のままです

6. 非同期メッセージのディスパッチと処理

これまでのところ、すべての例で同期メッセージディスパッチを使用しています。 post.now()を呼び出すと、メッセージはpost()を呼び出したのと同じスレッド内の各ハンドラーに配信されました。

6.1. 非同期ディスパッチ

MBassador.post()SyncAsyncPostCommandを返します。 このクラスには、次のようないくつかのメソッドがあります。

  • now() –メッセージを同期的にディスパッチします。すべてのメッセージが配信されるまで、通話はブロックされます

  • asynchronously() –メッセージパブリケーションを非同期で実行します

サンプルクラスで非同期ディスパッチを使用してみましょう。 これらのテストでは、コードを単純化するためにAwaitilityを使用します。

private MBassador dispatcher = new MBassador<>();
private String testString;
private AtomicBoolean ready = new AtomicBoolean(false);

@Test
public void whenAsyncDispatched_thenMessageReceived() {
    dispatcher.post("foobar").asynchronously();

    await().untilAtomic(ready, equalTo(true));
    assertNotNull(testString);
}

@Handler
public void handleStringMessage(String message) {
    this.testString = message;
    ready.set(true);
}

このテストではasynchronously()を呼び出し、AtomicBooleanawait()のフラグとして使用して、配信スレッドがメッセージを配信するのを待ちます。

await()の呼び出しをコメントアウトすると、配信スレッドが完了する前にtestStringをチェックするため、テストが失敗するリスクがあります。

6.2. 非同期ハンドラーの呼び出し

非同期ディスパッチにより、メッセージプロバイダーはメッセージが各ハンドラーに配信される前にメッセージ処理に戻ることができますが、それでも各ハンドラーを順番に呼び出し、各ハンドラーは前のハンドラーが完了するまで待機する必要があります。

これは、1つのハンドラーが高価な操作を実行する場合に問題を引き起こす可能性があります。

MBassadorは、非同期ハンドラー呼び出しのメカニズムを提供します。 このために構成されたハンドラーは、スレッドでメッセージを受信します。

private Integer testInteger;
private String invocationThreadName;
private AtomicBoolean ready = new AtomicBoolean(false);

@Test
public void whenHandlerAsync_thenHandled() {
    dispatcher.post(42).now();

    await().untilAtomic(ready, equalTo(true));
    assertNotNull(testInteger);
    assertFalse(Thread.currentThread().getName().equals(invocationThreadName));
}

@Handler(delivery = Invoke.Asynchronously)
public void handleIntegerMessage(Integer message) {

    this.invocationThreadName = Thread.currentThread().getName();
    this.testInteger = message;
    ready.set(true);
}

ハンドラーは、Handlerアノテーションのdelivery = Invoke.Asynchronouslyプロパティを使用して非同期呼び出しを要求できます。 テストでは、ディスパッチメソッドとハンドラーのThread名を比較してこれを確認します。

7. MBassadorのカスタマイズ

これまで、デフォルト構成でMBassadorのインスタンスを使用してきました。 コーディネーターの動作は、これまでに見たものと同様に、アノテーションを使用して変更できます。このチュートリアルを終了するために、さらにいくつか説明します。

7.1. 例外処理

ハンドラーはチェック例外を定義できません。 代わりに、ディスパッチャーには、コンストラクターへの引数としてIPublicationErrorHandlerを指定できます。

public class MBassadorConfigurationTest
  implements IPublicationErrorHandler {

    private MBassador dispatcher;
    private String messageString;
    private Throwable errorCause;

    @Before
    public void prepareTests() {
        dispatcher = new MBassador(this);
        dispatcher.subscribe(this);
    }

    @Test
    public void whenErrorOccurs_thenErrorHandler() {
        dispatcher.post("Error").now();

        assertNull(messageString);
        assertNotNull(errorCause);
    }

    @Test
    public void whenNoErrorOccurs_thenStringHandler() {
        dispatcher.post("Error").now();

        assertNull(errorCause);
        assertNotNull(messageString);
    }

    @Handler
    public void handleString(String message) {
        if ("Error".equals(message)) {
            throw new Error("BOOM");
        }
        messageString = message;
    }

    @Override
    public void handleError(PublicationError error) {
        errorCause = error.getCause().getCause();
    }
}

handleString()Error,をスローすると、errorCause.に保存されます

7.2. ハンドラーの優先順位

Handlers are called in reverse order of how they are added, but this isn’t behavior we want to rely on.スレッドでハンドラーを呼び出す機能があっても、ハンドラーが呼び出される順序を知る必要がある場合があります。

ハンドラーの優先順位を明示的に設定できます。

private LinkedList list = new LinkedList<>();

@Test
public void whenRejectDispatched_thenPriorityHandled() {
    dispatcher.post(new RejectMessage()).now();

    // Items should pop() off in reverse priority order
    assertTrue(1 == list.pop());
    assertTrue(3 == list.pop());
    assertTrue(5 == list.pop());
}

@Handler(priority = 5)
public void handleRejectMessage5(RejectMessage rejectMessage) {
    list.push(5);
}

@Handler(priority = 3)
public void handleRejectMessage3(RejectMessage rejectMessage) {
    list.push(3);
}

@Handler(priority = 2, rejectSubtypes = true)
public void handleMessage(Message rejectMessage)
    logger.error("Reject handler #3");
    list.push(3);
}

@Handler(priority = 0)
public void handleRejectMessage0(RejectMessage rejectMessage) {
    list.push(1);
}

ハンドラーは最高の優先度から最低の順に呼び出されます。 デフォルトの優先順位がゼロのハンドラーは最後に呼び出されます。 ハンドラー番号pop()が逆の順序でオフになっていることがわかります。

7.3. サブタイプを拒否する、簡単な方法

上記のテストでhandleMessage()はどうなりましたか? サブタイプをフィルタリングするためにRejectSubTypes.classを使用する必要はありません。

RejectSubTypesは、クラスと同じフィルタリングを提供するブールフラグですが、IMessageFilterの実装よりもパフォーマンスが優れています。

ただし、サブタイプのみを受け入れるために、フィルタベースの実装を使用する必要があります。

8. 結論

MBassadorは、オブジェクト間でメッセージを渡すためのシンプルで簡単なライブラリです。 メッセージはさまざまな方法で編成でき、同期的または非同期的にディスパッチできます。

そして、いつものように、例はthis GitHub projectで利用可能です。