春のイベント

1概要

この記事では、Springでのイベントの使用方法について説明します。

イベントは、フレームワーク内で見過ごされがちな機能の1つですが、より有用なものの1つです。そして、Springの他の多くのことと同様に、イベント発行は ApplicationContext. によって提供される機能の1つです。

従うべきいくつかの簡単なガイドラインがあります。

  • イベントは ApplicationEvent を拡張する必要があります

  • 発行者は ApplicationEventPublisher オブジェクトを注入する必要があります。

  • リスナーは ApplicationListener インターフェースを実装するべきです

2カスタムイベント

Springでは、カスタムイベントを作成して公開することができます。これは、デフォルトでは 同期です 。これにはいくつかの利点があります。たとえば、リスナーが発行者のトランザクションコンテキストに参加できるようになるなどです。

2.1. 簡単なアプリケーションイベント

  • 単純なイベントクラス** を作成しましょう - イベントデータを格納するための単なるプレースホルダーです。この場合、イベントクラスはStringメッセージを保持します。

public class CustomSpringEvent extends ApplicationEvent {
    private String message;

    public CustomSpringEvent(Object source, String message) {
        super(source);
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
}

2.2. 出版社

それでは、 そのイベントの発行者を作成しましょう 。発行者はイベントオブジェクトを作成し、それを聞いている人には誰でも発行できます。

イベントを発行するために、発行者は単に ApplicationEventPublisher を注入し、 publishEvent() APIを使用するだけです。

@Component
public class CustomSpringEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doStuffAndPublishAnEvent(final String message) {
        System.out.println("Publishing custom event. ");
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }
}

代わりに、publisherクラスは ApplicationEventPublisherAware インターフェースを実装することができます - これはアプリケーションの起動時にイベントパブリッシャーを注入するでしょう。通常、発行者に[email protected]を挿入するほうが簡単です。

2.3. リスナー

最後に、リスナーを作りましょう。

リスナーの唯一の要件は、Beanであり、 ApplicationListener インターフェースを実装することです。

@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
    @Override
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println("Received spring custom event - " + event.getMessage());
    }
}

私たちのカスタムリスナーがジェネリック型のカスタムイベントでどのようにパラメータ化されているかに注目してください。

これにより、オブジェクトが特定のイベントクラスのインスタンスであるかどうかを確認してキャストする必要もなくなります。

そして、すでに説明したように - デフォルトで スプリングイベントは同期 - doStuffAndPublishAnEvent() メソッドは、すべてのリスナーがイベントの処理を終了するまでブロックします。

3非同期イベントの作成

場合によっては、イベントを同期的に公開することが、実際には探しているものではありません - イベントの非同期処理 が必要な場合があります。

エグゼキュータを使って ApplicationEventMulticaster Beanを作成することで、設定でこれを有効にできます。ここでの目的のために、 SimpleAsyncTaskExecutor がうまく機能します。

@Configuration
public class AsynchronousSpringEventsConfig {
    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster
          = new SimpleApplicationEventMulticaster();

        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }
}

イベント、パブリッシャー、およびリスナーの実装は以前と同じままですが、今では リスナーは別のスレッドで非同期にイベントを処理します

4既存のフレームワークイベント

Spring自体が箱から出してさまざまなイベントを公開します。たとえば、 ApplicationContext はさまざまなフレームワークイベントを発生させます。例えば。

ContextRefreshedEvent、ContextStartedEvent、RequestHandledEvent など

これらのイベントは、アプリケーション開発者に、アプリケーションのライフサイクルとコンテキストを把握し、必要に応じて独自のカスタムロジックを追加するためのオプションを提供します。

これは、コンテキストの更新を待機しているリスナーの簡単な例です。

public class ContextRefreshedListener
  implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent cse) {
        System.out.println("Handling context re-freshed event. ");
    }
}

既存のフレームワークイベントの詳細については、https://www.baeldung.com/spring-context-events[次のチュートリアルはこちら]をご覧ください。

5アノテーション駆動型イベントリスナ

Spring 4.2以降、イベントリスナは ApplicationListener インタフェースを実装するBeanである必要はなく、 @ EventListener アノテーションを介してマネージドBeanの任意の public メソッドに登録できます。

@Component
public class AnnotationDrivenContextStartedListener {
   //@Async
    @EventListener
    public void handleContextStart(ContextStartedEvent cse) {
        System.out.println("Handling context started event.");
    }
}

前述のように、メソッドシグネチャは、それが消費するイベントタイプを宣言します。以前と同様に、このリスナーは同期的に呼び出されます。しかし、非同期にすることは @ Async アノテーションを追加するのと同じくらい簡単です(アプリケーションにhttps://www.baeldung.com/spring-async#enable-async-support[enable Async support]を忘れないでください)。

** 6. ジェネリックサポート

イベントタイプの総称情報を使用してイベントを送出することもできます。

6.1. 一般的なアプリケーションイベント

  • 一般的なイベントタイプを作成しましょう** 。この例では、イベントクラスは任意のコンテンツと success ステータスインジケータを保持します。

public class GenericSpringEvent<T> {
    private T what;
    protected boolean success;

    public GenericSpringEvent(T what, boolean success) {
        this.what = what;
        this.success = success;
    }
   //... standard getters
}

GenericSpringEvent CustomSpringEvent の違いに注目してください。これで任意のイベントを公開できるようになり、 ApplicationEvent から拡張する必要がなくなりました。

6.2. リスナー

それでは そのイベントのリスナー を作成しましょう。以前のようにApplicationListener__インターフェースを実装することでリスナーを定義できます。

@Component
public class GenericSpringEventListener implements ApplicationListener<GenericSpringEvent<String>> {
    @Override
    public void onApplicationEvent(@NonNull GenericSpringEvent<String> event) {
        System.out.println("Received spring generic event - " + event.getWhat());
    }
}

しかし残念ながら、この定義では、 ApplicationEvent クラスから GenericSpringEvent を継承する必要があります。それでは、このチュートリアルでは、 前述 で説明したアノテーション駆動型のイベントリスナーを利用しましょう。

@ EventListener アノテーションにブール値のSpEL式を定義することで イベントリスナを条件付き にすることも可能です。この場合、イベントハンドラーは String GenericSpringEvent が成功した場合にのみ呼び出されます。

@Component
public class AnnotationDrivenEventListener {
    @EventListener(condition = "#event.success")
    public void handleSuccessful(GenericSpringEvent<String> event) {
        System.out.println("Handling generic event (conditional).");
    }
}

Spring Expression Language(SpEL) は、別のチュートリアルで詳しく説明されている強力な式言語です。

6.3. 出版社

イベント発行者は、リンク#publisher[上記]のリンクと似ています。しかし、型が消去されるため、フィルタリングするジェネリックスパラメータを解決するイベントを発行する必要があります。たとえば、 class GenericStringSpringEventはGenericSpringEvent <String> を拡張します。

そして、 イベントを公開するための別の方法 があります。結果として @ EventListener というアノテーションが付けられたメソッドからnull以外の値を返すと、Spring Frameworkはその結果を新しいイベントとして私たちに送信します。

さらに、イベント処理の結果としてそれらをコレクションに戻すことで、複数の新しいイベントを発行することができます。

7. トランザクションバウンドイベント

この段落は @ TransactionalEventListener アノテーションの使用についてです。トランザクション管理の詳細については、https://www.baeldung.com/transaction-configuration-with-jpa-and-spring[Spring and JPAとのトランザクション]チュートリアルをご覧ください。

Spring 4.2以降、フレームワークは新しい @ TransactionalEventListener アノテーションを提供します。これは @ EventListener の拡張で、イベントのリスナーをトランザクションのフェーズにバインドすることを可能にします。以下のトランザクションフェーズにバインドすることができます。

トランザクションが発生した場合、イベントを起動するために** AFTER COMMIT__(デフォルト)が使用されます

  • 正常に完了しました**

  • AFTER ROLLBACK__ - トランザクションが ロールバックした場合

  • AFTER COMPLETION__ - トランザクションが 完了 した場合(** のエイリアス

AFTER COMMIT および AFTER ROLLBACK BEFORE COMMIT__は、トランザクションの直前に イベントを起動するために使用されます。

  • コミット**

トランザクションイベントリスナの簡単な例を示します。

@TransactionalEventListener(phase = TransactionPhase.BEFORE__COMMIT)
public void handleCustom(CustomSpringEvent event) {
    System.out.println("Handling event inside a transaction BEFORE COMMIT.");
}

このリスナーは、イベントプロデューサが実行されていてコミットしようとしているトランザクションがある場合にのみ呼び出されます。

また、トランザクションが実行されていない場合、 fallbackExecution 属性を true に設定してこれをオーバーライドしない限り、イベントはまったく送信されません。

8結論

この簡単なチュートリアルでは、 Springでのイベント処理 - 簡単なカスタムイベントの作成、公開、そしてリスナでの処理の基本について説明しました。

構成内のイベントの非同期処理を有効にする方法についても簡単に説明しました。

次に、Spring 4.2で導入された、アノテーション主導のリスナー、より優れたジェネリックスのサポート、トランザクションフェーズに結びつくイベントなど、改良点について学びました。

いつものように、この記事で紹介されているコードはhttps://github.com/eugenp/tutorials/tree/master/spring-all[Githubで利用可能]です。これはMavenベースのプロジェクトなので、そのままインポートして実行するのは簡単なはずです。