エキサイト株式会社エンジニアの佐々木です。2021年Exciteホールディングスアドベントカレンダー17日目を担当させていただきます。
SpringBootでのイベントリスナーの実装についてです。
はじめに
SpringBootでは、アノテーションを用いたイベントリスナーの実装が比較的簡単にできるかと思います。
コード
今回は、イベント、パブリッシャー、リスナーと3つのクラスを書いていきます。
イベント
イベントのオブジェクトのコードは下記になります。
public class DemoEvent extends ApplicationEvent { private String message; public DemoEvent(Object source, String name) { super(source); this.message = name; } public String getMessage(){ return this.message; } }
ApplicationEvent
を継承して、あとはそれに必要な要素やメッセージを取得するメソッドをつけてあげれば実装完了です。
パブリッシャー
イベント発火を管理するパブリッシャーは下記のように記載します。
@Component @AllArgsConstructor public class DemoPublisher { private final ApplicationEventPublisher publisher; // Springで用意されている public void publishEvent(String name){ publisher.publishEvent(new DemoEvent(this, name)); } }
Spring側で用意されているApplicationEventPublisher
をDIし、そこにイベントのオブジェクトをセットするメソッドを実装しておきます。
リスナー
リスナーになります。パブリッシャーが発火したイベントを受け取るオブジェクトになります。
@Component @AllArgsConstructor @Slf4j public class DemoListener { @EventListener // Springで用意されているアノテーションを付与 public void onEvent(DemoEvent demoEvent){ // 引数にイベントのオブジェクトを設定する log.info("class: {} , event: {} , timestamp: {} ", new Object(){}.getClass().getEnclosingClass().getName(), demoEvent.getMessage(), demoEvent.getTimestamp() ); } }
リスナークラス自体を@Component
でDIコンテナに登録し、実行したいメソッドに、@EventListner
を付与し、引数にイベントオブジェクトを設定します。
パブリッシャーの呼び出し
パブリッシャーを呼び出す処理を記述します。
@RestController @RequestMapping @AllArgsConstructor public class DemoController { private final DemoPublisher publisher; // パブリッシャーをDIする @GetMapping public void index(){ publisher.publishEvent("index"); } }
今回は、コントローラにリクエストがあったら、パブリッシャーを呼んでもらう実装にしています。
実行
$ curl http://localhost:8080/ 2021-12-17 07:59:16.248 INFO 14746 --- [nio-8080-exec-1] c.e.excite20211212.listner.DemoListener : class: com.example.excite20211212.listner.DemoListener , event: index , timestamp: 1639695556247
このようなログが出力されます。ちゃんとDemoListenerからログがでていることがわかります。
リスナーを増やす
アプリケーションが大きくなってきて、リスナーの処理を増やしたいが、別々で処理したいとします。その場合パブリッシャーとリスナーをそれぞれ修正せずとも、リスナーだけ増やせば可能になります。
@Component @AllArgsConstructor @Slf4j public class Demo2Listener { //クラス名変更 @EventListener public void onEvent2(DemoEvent demoEvent){ // メソッド名も変更(変更しなくてもOK) log.info("class: {} , event: {} , timestamp: {} ", new Object(){}.getClass().getEnclosingClass().getName(), demoEvent.getMessage(), demoEvent.getTimestamp()); } }
これで、パブリッシャーに気付かれずにリスナーを増やすことが可能になりました。
$ curl http://localhost:8080/ 2021-12-17 08:09:34.669 INFO 15034 --- [nio-8080-exec-2] c.e.e.listner.Demo2Listener : class: com.example.excite20211212.listner.Demo2Listener , event: index , timestamp: 1639696174669 2021-12-17 08:09:34.669 INFO 15034 --- [nio-8080-exec-2] c.e.excite20211212.listner.DemoListener : class: com.example.excite20211212.listner.DemoListener , event: index , timestamp: 1639696174669
まとめ
SpringのDIコンテナのおかげで、比較的簡単にイベント処理を疎結合に実装することが可能になりました。
おまけ
イベントは、ApplicationEvent
の継承がなくても動作しました。
public class DemoEvent { private String message; public DemoEvent(Object source, String name) { this.message = name; } public String getMessage(){ return this.message; } }
ApplicationEvent
は、実行時間やシリアライズのインタフェースを実装してくれているので、継承はしていた方がいいと思いますが、DIがよしなに解釈して実行できていました。
最後に
エキサイトホールディングスアドベントカレンダー17日目を担当させていただきました。読んでいただいてありがとうございます。引き続き、アドベントカレンダーをお楽しみいただけると幸いです。
エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。
カジュアル面談はこちらになります! meety.net
募集職種一覧はこちらになります!(カジュアルからもOK) www.wantedly.com