Spring Bootで非同期に処理を実行する


ミドルウェアを使って非同期とかではなく、単にSpring Boot単体で非同期に実行する方法。以下の点を守ればOKなはず。

  • @Configurationなクラスに@EnableAsyncアノテーションを付与
  • 非同期実行用のスレッドをどう生成するかを定めるため、Executorを定義
  • 非同期に実行したいメソッドに@Asyncアノテーションを付与

@Configurationなクラスに@EnableAsyncアノテーションを付与

@EnableAsyncアノテーションがないと非同期実行できないため、必ず付与する。

@Configuration
@EnableAsync
class AsyncConfig {
  // ...
}

@Configurationなクラスなら何でも良いため、以下でもOK。

@SpringBootApplication
@EnableAsync
class SomeApplication

が、Executorを定義するためにも専用の設定に切り出しておいた方が何かと便利だと思う。

非同期実行用のスレッドをどう生成するかを定めるため、Executorを定義

デフォルトだと非同期実行をしようとするたびにスレッドを生成する仕様。スレッドプールなどを使用するには、そうなるよう定義を加える必要がある。

@Configuration
@EnableAsync
class AsyncConfig : AsyncConfigurerSupport() {

  @Bean
  override fun getAsyncExecutor(): Executor {
    val executor = ThreadPoolTaskExecutor()
    // initialize executor
    return executor
  }
}

非同期に実行したいメソッドに@Asyncアノテーションを付与

そのまんま。

@Service
class SomeService {

  @Async
  fun execute() {
    // ...
  }
}

ちなみにクラス自体に@Asyncアノテーションを付与すればメソッドすべてが非同期実行になる。

余談

動作確認のために超適当に以下のようなコードを書いたら非同期実行にならなくて困った。

@Controller
class SomeController {
  @RequestMapping(/* ... */)
  fun execute(): String {
    // ...
    val service = SomeService()
    service.execute()
    // ...
  }
}

これはSomeServiceのインスタンスを直接生成しているのが原因。普通は下記のようにDIコンテナ経由で取得するのが自然なはずなので、困ることないとは思うけど一応。私がSpring Bootに不慣れというのがバレバレだな!

@Controller
class SomeController(private val service: SomeService) {
  @RequestMapping(/* ... */)
  fun execute(): String {
    // ...
    service.execute()
    // ...
  }
}