SpringFrameworkには、Spring管理下にあるインスタンスの実行を「イベント」として登録し、イベント発行で実行を制御できる仕組みが用意されています。使うのも単純ですし、SpringBootでは非同期処理にもできます。今回はSpringBootで非同期イベントを使った非同期処理の実現例を記載します。
動作環境
- Java 11
- SpringBoot 2.3.1
イベント駆動処理に必要なこと
以下3つの作成が必須です
- 処理実行に必要なパラメータ類を渡すEventクラス
- イベントを起動させるPublisherクラス
- イベントを検知して処理を開始するListenerクラス
実装例
今回作成するイベント:呼び出し元のクラスから文字列を受け取り、ログに出力する
イベントから受け渡す文字列を格納するクラスは以下を用います。
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 を継承します。
@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と簡単です。
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でリクエストを受けて、イベントを実行してから、画面を表示する例です。
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
を付与します。
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メソッドにします。
public class DisplayController {
private final PrimaryEventPublisher publisher;
@GetMapping
public ModelAndView display(ModelAndView mnv) {
// 画面表示とは別に処理をさせたい
publisher.ignite("メッセージを送信します");
mnv.setViewName("display");
return mnv;
}
}
PrimaryEventPublisherを非同期に動かしたいので、@Async
を付けるだけです。
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
を使ってデータソースに出力も可能ですから、何らかの処理終了通知を社内チャットツールやメールに送信、なんてのにも使えます。
-
実際にはSpringのApplicationContextから取得できますので、
@Component
などのSpring管理下に置くためのアノテーション付与は必須ではありません。「Spring管理下=ApplicationContextに登録しているクラス」です。なお、SpringBootではSpringのアノテーションである@Controller
、@Service
、@Repository
、@Component
が付与されたクラスの存在を検出する ComponentScan の機能が有効なパッケージ内にあることが前提です。 ↩ -
Spring管理下にある ApplicationEventPublisher から実行されるため。ApplicationContextに登録しておかなければ動作しません(空振りします) ↩