LoginSignup
6
7

More than 3 years have passed since last update.

SpringBootの非同期イベント発行に必要な手順

Posted at

SpringFrameworkには、Spring管理下にあるインスタンスの実行を「イベント」として登録し、イベント発行で実行を制御できる仕組みが用意されています。使うのも単純ですし、SpringBootでは非同期処理にもできます。今回はSpringBootで非同期イベントを使った非同期処理の実現例を記載します。

動作環境

  • Java 11
  • SpringBoot 2.3.1

イベント駆動処理に必要なこと

以下3つの作成が必須です

  • 処理実行に必要なパラメータ類を渡すEventクラス
  • イベントを起動させるPublisherクラス
  • イベントを検知して処理を開始するListenerクラス

実装例

今回作成するイベント:呼び出し元のクラスから文字列を受け取り、ログに出力する

イベントから受け渡す文字列を格納するクラスは以下を用います。

SampleMessage.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

@AllArgsConstructor @Getter @ToString
public class SampleMessage {
    private String message;

    public static SampleMessage of(String message) {
        return new SampleMessage(message);
    }
}

Event

org.springframework.context.ApplicationEvent を継承します。

PrimaryEvent.java
@Getter
public class PrimaryEvent extends ApplicationEvent {

    private final SampleMessage sampleMessage;

    public PrimaryEvent(Object source,  SampleMessage sampleMessage) {
        super(source);
        this.sampleMessage = sampleMessage;
    }

}

コンストラクタの第1引数:Object sourceは必須実装です。

Publisher

Springが管理している org.springframework.context.ApplicationEventPublisher インスタンスを使ったJavaクラスを作ります。そのため @Component@Serviceなど付与してSpring管理に置いたクラスにする1と簡単です。

PrimaryEventPublisher.java
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;

@Component
@AllArgsConstructor
public class PrimaryEventPublisher {

    private final ApplicationEventPublisher applicationEventPublisher;

    public void ignite(String message) {
        SampleMessage sampleMessage = SampleMessage.of(message);

        // イベント作成する
        PrimaryEvent event = new PrimaryEvent(this, sampleMessage);

        // イベント発行!
        applicationEventPublisher.publishEvent(event);
    }
}

Listener

Spring管理下2に置き、org.springframework.context.ApplicationListenerインタフェースを実装します。
一番簡単なのは@Componentを付与します。

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import lombok.extern.log4j.Log4j2;

@Component
public class PrimaryEventListener implements ApplicationListener<PrimaryEvent>{

    @Override
    public void onApplicationEvent(PrimaryEvent event) {
        SampleMessage sampleMessage = event.getSampleMessage();

        // メッセージを受けた後の後続処理↓......
    }

}

以上でイベント実行の準備が完了です。

SpringMVCのControllerからの実行例

これはControllerでリクエストを受けて、イベントを実行してから、画面を表示する例です。

DisplayController.java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import lombok.AllArgsConstructor;

@RequestMapping("/display")
@Controller
@AllArgsConstructor
public class DisplayController {

    private final PrimaryEventPublisher publisher;

    @GetMapping
    public ModelAndView display(ModelAndView mnv) {

        publisher.ignite("メッセージを送信します");

        mnv.setViewName("display");
        return mnv;
    }
}

これだけですとSpringMVCを使った実装と何ら変わらず、イベントが実行されて終了した後にControllerは画面を表示します。

非同期処理にする

SpringBootで非同期処理を使うには、@EnableAsyncをSpringBootの起動クラスに付与した後、非同期処理にしたいクラスに@Asyncを付与します。

SpringEventSampleApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class SpringEventSampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringEventSampleApplication.class, args);
    }

}

例えば、非同期で動作させたいのはController内にある以下のigniteメソッドにします。

DisplayController.java
public class DisplayController {

    private final PrimaryEventPublisher publisher;

    @GetMapping
    public ModelAndView display(ModelAndView mnv) {
        // 画面表示とは別に処理をさせたい
        publisher.ignite("メッセージを送信します");

        mnv.setViewName("display");
        return mnv;
    }
}

PrimaryEventPublisherを非同期に動かしたいので、@Asyncを付けるだけです。

PrimaryEventPublisher.java
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import lombok.AllArgsConstructor;

@Component
@AllArgsConstructor
@Async
public class PrimaryEventPublisher {

    private final ApplicationEventPublisher applicationEventPublisher;

    public void ignite(String message) {
        SampleMessage sampleMessage = SampleMessage.of(message);

        PrimaryEvent event = new PrimaryEvent(this, sampleMessage);
        applicationEventPublisher.publishEvent(event);
    }
}

非常に簡単ですね d(・ω・
実行できる処理には@Repositoryを使ってデータソースに出力も可能ですから、何らかの処理終了通知を社内チャットツールやメールに送信、なんてのにも使えます。


  1. 実際にはSpringのApplicationContextから取得できますので、@ComponentなどのSpring管理下に置くためのアノテーション付与は必須ではありません。「Spring管理下=ApplicationContextに登録しているクラス」です。なお、SpringBootではSpringのアノテーションである@Controller@Service@Repository@Componentが付与されたクラスの存在を検出する ComponentScan の機能が有効なパッケージ内にあることが前提です。 

  2. Spring管理下にある ApplicationEventPublisher から実行されるため。ApplicationContextに登録しておかなければ動作しません(空振りします) 

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7