Spring @EventListener の仕組みを完全解読

Spring Framework において、ビジネスロジックを疎結合に保ちながら拡張可能にするための手段として「イベント駆動アーキテクチャ」がよく使われる。特に @EventListener を使えば、わずか数行でイベントの購読(リスニング)を実装できるが、その背後で何が起きているのかは意外と知られていない。

動作確認用サンプル

まず最小構成で動作を確認する。

イベントクラス

public class UserRegisteredEvent {
    private final String username;
    public UserRegisteredEvent(String username) {
        this.username = username;
    }
    public String getUsername() {
        return username;
    }
}

イベントリスナー

@Component
public class NotificationListener {

    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        System.out.printf("ユーザー %s の登録が完了しました。SMS を送信します。%n",
                          event.getUsername());
    }
}

イベント発行

@RestController
@RequiredArgsConstructor
public class RegisterController {
    private final ApplicationEventPublisher publisher;

    @GetMapping("/register")
    public String register(@RequestParam String name) {
        // 本来は DB などに永続化
        publisher.publishEvent(new UserRegisteredEvent(name));
        return "registered";
    }
}

起動後 /register?name=Taro を叩くと、コンソールに「ユーザー Taro の登録が完了しました。SMS を送信します。」と出力される。

内部フローを追う

1. イベント発行 → リスナー呼び出しまで

ApplicationEventPublisher#publishEvent が呼ばれると、内部では以下の流れでリスナーが実行される。

  1. SimpleApplicationEventMulticaster#multicastEvent が呼び出される。
  2. 登録済みの ApplicationListener を取得し、イベント型がマッチするものだけをフィルタリング。
  3. マッチしたリスナーに対して onApplicationEvent を呼び出す。
  4. @EventListener が付与されたメソッドは、内部で生成された ApplicationListenerMethodAdapter が代理して実行する。

2. リスナーがどこから来るのか

起動時に Spring は以下の処理を行う。

  1. EventListenerMethodProcessor(BeanFactoryPostProcessor)が登録される。
  2. 全 Bean をスキャンし、@EventListener メソッドを検出。
  3. 検出したメソッドごとに ApplicationListenerMethodAdapter を生成し、beanNameMethodeventType を保持。
  4. 生成したアダプタを SimpleApplicationEventMulticaster に追加。

つまり、起動時点で「どの Bean のどのメソッドがどのイベントを受け取るか」が静的に決まり、実行時は単純にルックアップするだけになる。

3. デバッグテクニック

起動時のフレームワークイベントで何度も止まらないようにするには、条件付きブレークポイントを活用する。

event instanceof PayloadApplicationEvent
    && ((PayloadApplicationEvent) event).payload instanceof UserRegisteredEvent

これにより、自分が発行したイベントのみで停止できる。

非同期実行・条件分岐・トランザクション境界

非同期実行

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public TaskExecutor taskExecutor() {
        return new SimpleAsyncTaskExecutor();
    }
}

@EventListener
@Async
public void onUserRegisteredAsync(UserRegisteredEvent event) {
    // 別スレッドで実行
}

条件分岐

@EventListener(condition = "#event.username.length() > 5")
public void onLongUsername(UserRegisteredEvent event) {
    // ユーザー名が 6 文字以上の場合のみ実行
}

トランザクション境界でのイベント

@EventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterTx(UserRegisteredEvent event) {
    // トランザクション完了後に実行
}

内部的には ApplicationListenerMethodTransactionalAdapter がトランザクション同期を行う。

まとめ

@EventListener は「メソッド+アノテーション」という最小の記述で、起動時に Spring が自動的に ApplicationListener へ変換し、イベントマルチキャスタに登録する仕組みである。実行時はイベント型をキーにして高速にディスパッチされ、さらに非同期・条件・トランザクション境界などの拡張も容易に行える。

タグ: Spring Framework ApplicationEvent EventListener ApplicationListenerMethodAdapter SimpleApplicationEventMulticaster

7月5日 22:49 投稿