初めに
Spring BootでApplicationRunnerインターフェースを用いてバッチ処理を実装する機会があった。
Application Runnerがいつ実行されるのかパッとわからなかったので調査を行った。
(動かしてみればなんとなく理解できるもののもうちょっと理解度を上げたい)
ここではApplicationRunnerインターフェースを実装したbeanをApplicationRunnerと呼ぶこととする。
前提
本稿は以下のバージョンをベースに記述している。
- Spring Boot V3.0.1
結論
ApplicationRunnerはSpring Bootアプリケーション起動時の最後の処理として実行される。
もし複数存在すれば全てを実行する。(アノテーション(@Order)などで順番を明示していればその順番で実行される)
Application Runnerとは
Application Runnerとは、以下のインターフェースを実装したbeanである。
Interface used to indicate that a bean should run when it is contained within a SpringApplication. Multiple ApplicationRunner beans can be defined within the same application context and can be ordered using the Ordered interface or @Order annotation.
Spring Boot 公式Docs "Interface ApplicationRunner"」
定義を見るとSpringApplicationに含まれたときに実行すべきbeanであることを示すことに用いるインターフェースであることがわかる。
しかしこれだけでは具体的にいつ実行されるのかがわからない。
次にGitHubにあるSpring BootプロジェクトSpringApplication.javaのソースコードからいつ実行されるのかを明らかにする。
SpringApplication.javaを開く
Spring Bootソースコード
Spring Bootのエントリポイントを抜き出す。("MyApplication.class"はここのアプリの実装に依る)
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
ここでは、SpringApplication.run関数の中身を追っていく。
以下のrun関数がアプリ側で呼び出されるものである。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
上記関数でラップされているrun関数を読み進めていく
public ConfigurableApplicationContext run(String... args) {
// 省略...
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
// 省略...
return context;
}
起動時に呼ばれるrun関数にあるcallRunners(context, applicationArguments)でApplicationRunnerは、実行される。
次にcallRunners関数の内部を見る。
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner applicationRunner) {
callRunner(applicationRunner, args);
}
if (runner instanceof CommandLineRunner commandLineRunner) {
callRunner(commandLineRunner, args);
}
}
}
ここでDIコンテナからApplicationRunnerを取得し逐次実行している。(callRunner関数で実行)
次にcallRunner関数をみてみる。
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
ここでApplicationRunnerのRunメソッドを呼んでいる。ここで具体的な実装(例えばバッチ処理)が実行されることがわかる。
ソースコードを読み進めると具体的にいつApplicationRunnerが実行されているのかがわかった!
まとめ
ApplicationRunnerはSpringBootアプリケーション起動時の最後に実行される。
※厳密にいうと最後ではないが例外時などの後処理を除くと最後に当たるので最後と記載した
参考文献
文中に記載の通りなので割愛する。
所感
OSSは処理を追うことができるので素晴らしいなあと思った。
Spring Bootの知識はまだ拙いのでより深い知識・理解を備えた上で実装できるようにしていきたい。